22時に寝ようと思って2時に寝る。

備忘録や日記を書いてます。きょうは早く寝よう。

Rails5 - utf8mb4(主にiOSの絵文字)に対応する

実行環境

文字コードのutf8mb4に対応する

今回、主にiOSの絵文字に対応するためにutf8mb4に変更する必要がありました。MySQLであればバージョン5.5以降でutf8mb4をサポートしています。

以下がそのために行った設定です。

config/database.ymlを設定する

default: &default
  adapter: mysql2
  pool: 5
  timeout: 5000
  encoding: utf8mb4
  collation: utf8mb4_general_ci
  socket: /var/lib/mysql/mysql.sock

今回、重要なのはencodingcollationです。それぞれの大まかな意味としては、以下のとおりです。

MySQL自体の文字コード設定を確認する

show variables like 'char%';で現在の全体的なMySQL文字コード関連の設定を参照できます。

MariaDB [(none)]> show variables like 'char%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8                       |
| character_set_connection | utf8                       |
| character_set_database   | latin1                     |
| character_set_filesystem | binary                     |
| character_set_results    | utf8                       |
| character_set_server     | latin1                     |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)

utf8mb4には対応できていないので、設定する必要があります。

文字コードutf8mb4に変更する

/etc/my.cnf.d/server.cnfにあたる設定ファイルを編集します。

$ sudo vi /etc/my.cnf.d/server.cnf
[server]
character-set-server = utf8mb4

[mysqld]
character-set-server = utf8mb4
plugin-load = handlersocket.so

[client]
default-character-set = utf8mb4

再起動して、文字コードが反映されたか確認してみます。

$ sudo service mysql stop
Shutting down MySQL... SUCCESS!

$ sudo service mysql start
Starting MySQL.170531 23:11:49 mysqld_safe Logging to '/var/lib/mysql/localhost.localdomain.err'.
170531 23:11:49 mysqld_safe Starting mysqld daemon with databases from /var/lib/mysql
 SUCCESS!

$ mysql -uroot -p

MariaDB [(none)]> show variables like 'char%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8mb4                    |
| character_set_connection | utf8mb4                    |
| character_set_database   | utf8mb4                    |
| character_set_filesystem | binary                     |
| character_set_results    | utf8mb4                    |
| character_set_server     | utf8mb4                    |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)

うまく設定が反映されているのがわかります。character_set_systemutf8のままですが、こちらの変数はシステムの使用する文字セットで常にutf8が使用される設定のため問題ありません。

試しにマイグレーションしてみる

今回は新たにマイグレーションしてみることで、utf8mb4に対応できているかを確認してみます。マイグレーション生成は省略します。

$ rake db:migrate

rake aborted!
StandardError: An error has occurred, all later migrations canceled:
Mysql2::Error: Specified key was too long; max key length is 767 bytes: CREATE UNIQUE INDEX `index_piyo`  ON `piyo_tables` (`hoge`, `piyo`)

Tasks: TOP => db:migrate
(See full trace by running task with --trace)

Mysql2::Error: Specified key was too long; max key length is 767 bytesといったエラーが出ます。

Mysql2::Error: Specified key was too long; max key length is 767 bytesを解決する

このエラー自体は以下の説明が分かりやすいです。

デフォルトでは最大インデックス長は767バイトですので、UTF8では255文字まで、UTF8MB4では191文字までしかインデックスを張れません。 しかし、こちらはMySQL 5.5.14以降からINNODB_LARGE_PREFIXオプションで最大3072バイトまで拡張できるので、そんなに大きな問題ではないです。

RailsとMySQLでiOSの絵文字に対応(UTF8MB4化)した話 - Akata Works

これに対応するために、MySQLの設定ファイルにINNODB_LARGE_PREFIXオプションを追加し、インデックスのキープレフィックスを拡張します。

$ sudo vi /etc/my.cnf

# 以下を追加
[mysqld]
innodb_file_format = Barracuda
innodb_file_per_table = 1
innodb_large_prefix

主に3つの指定をしています。

  • innodb_large_prefixを有効にする
    • 上記設定を有効にするために
      • innodb_file_formatBarracudaにする
      • innodb_file_per_tableを有効にする

設定後、MySQLを再起動します。

$ sudo service mysql stop
Shutting down MySQL.. SUCCESS!
$ sudo service mysql start
Starting MySQL.170531 23:24:12 mysqld_safe Logging to '/var/lib/mysql/localhost.localdomain.err'.
170531 23:24:12 mysqld_safe Starting mysqld daemon with databases from /var/lib/mysql
 SUCCESS!

これでおそらく、インデックスのキープレフィックスが拡張できました。

テーブル作成時にオプションを追加するようにパッチを当てる

kamipoさんのモンキーパッチを使い、テーブル作成時のオプションを指定します。

config/initializers/ar_innodb_row_format.rb

ActiveSupport.on_load :active_record do
  module ActiveRecord::ConnectionAdapters

    class AbstractMysqlAdapter
      def create_table_with_innodb_row_format(table_name, options = {})
        table_options = options.merge(:options => 'ENGINE=InnoDB ROW_FORMAT=DYNAMIC')
        create_table_without_innodb_row_format(table_name, table_options) do |td|
          yield td if block_given?
        end
      end
      alias_method_chain :create_table, :innodb_row_format
    end

  end
end

再度、マイグレーションしてみる

$ rake db:migrate:reset
Dropped database 'piyo_development'
Dropped database 'piyo_test'
Created database 'piyo_development'
Created database 'piyo_test'
== 20170531130735 CreatePiyos: migrating =========================
-- create_table(:piyos)
   -> 0.0109s
== 20170531130735 CreatePiyors: migrated (0.0191s) ================

完了できました。

db/schema.rbにダンプされたテーブルを見てみる

ActiveRecord::Schema.define(version: 20170531130735) do

  create_table "piyos", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" do |t|
    省略
  end

end

うまく設定ができています。 以上でutf8mb4対応は完了です。

参考