はじめに結論
db:create 実行時に、config/database.yml の encoding 設定に基づき、データベースレベルで設定される。
なので、Rails (ActiveRecord) のマイグレーションでは以下に留意する。
- config/database.yml に
encoding設定があるか?- この設定が存在しない場合、
db:migrateで作成されるテーブルのDEFAULT CHARSETが意図しないものになる可能性がある(Rails 5.1.4 ではutf8になる)
- この設定が存在しない場合、
- データベースが
db:createによって作成されているか?- 他手段(SQL直実行など)で作成した場合、
db:migrateで作成されるテーブルのDEFAULT CHARSETが config/database.yml のencoding設定の値と一致しなくなる
- 他手段(SQL直実行など)で作成した場合、
-
db:create実行後に config/database.yml のencodingを変えたりしていないか?- この状態で
db:drop db:createせずにdb:migrateを実行すると、新規作成されるテーブルのDEFAULT CHARSETは 以前のencodingの設定値になる
- この状態で
あるいは、以下のようにマイグレーションスクリプト側で DEFAULT CHARSET を含むオプションを明記するアプローチもある。こうすれば、上述の留意点が不要になる。
class CreateExamples < ActiveRecord::Migration[5.1]
def change
create_table :examples, options: "DEFAULT CHARSET=utf8mb4" do |t|
...
end
end
end
どういうことなのか
MySQLでは、テーブル作成時の DEFAULT CHARSET が以下の2段階で定義される。
上記のうち、character_set_database はデータベース毎に異なる値を持つことができる。各データベースで何が設定されているかは、例えば以下の方法で確認できる。
-
SHOW VARIABLES構文:mysql> USE some_database; Database changed mysql> SHOW VARIABLES LIKE 'character_set_database'; -- 現在選択中のデータベースによって値が変わる +------------------------+---------+ | Variable_name | Value | +------------------------+---------+ | character_set_database | utf8mb4 | +------------------------+---------+ -
SHOW CREATE DATABASE構文:mysql> SHOW CREATE DATABASE some_database; +---------------+---------------------------------------------------------------------------+ | Database | Create Database | +---------------+---------------------------------------------------------------------------+ | some_database | CREATE DATABASE `some_database` /*!40100 DEFAULT CHARACTER SET utf8mb4 */ | +---------------+---------------------------------------------------------------------------+
この character_set_database に相当する値は、データベース作成時に以下のようにして設定できる。
mysql> CREATE DATABASE some_database DEFAULT CHARACTER SET utf8mb4;
Query OK, 1 row affected (0.00 sec)
このデータベースに対して DEFAULT CHARSET オプションの無いテーブルを作成した場合、DEFAULT CHARSET には character_set_database の値が設定される。
mysql> CREATE TABLE examples (id bigint AUTO_INCREMENT PRIMARY KEY);
Query OK, 0 rows affected (0.00 sec)
mysql> SHOW CREATE TABLE examples\G
*************************** 1. row ***************************
Table: examples
Create Table: CREATE TABLE `examples` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
そして Rails (ActiveRecord) マイグレーションでは、db:create 実行時に config/database.yml の encoding 設定に基づき character_set_database 相当の値を設定するように実装されているようだ。更に、encoding 設定が無い場合は utf8 を設定するようだ。
-
activerecord-5.1.4: lib/active_record/tasks/mysql_database_tasks.rb#L12
def create establish_connection configuration_without_database connection.create_database configuration["database"], creation_options ... -
activerecord-5.1.4: lib/active_record/tasks/mysql_database_tasks.rb#L87
def creation_options Hash.new.tap do |options| options[:charset] = configuration["encoding"] if configuration.include? "encoding" options[:collation] = configuration["collation"] if configuration.include? "collation" end end -
activerecord-5.1.4: lib/active_record/connection_adapters/abstract_mysql_adapter.rb#L277
def create_database(name, options = {}) if options[:collation] execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')} COLLATE #{quote_table_name(options[:collation])}" else execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}" end end
上記は 5.1.4 (2018/01/17 時点での最新版) での実装だが、例えば Qiitaのこのエントリ を見ると 4.2系でも同様の実装のようだ。
どのようにハマり得るのか
例えば、事前にSQL直実行でデータベースを作成し(この時点で作成したデータベースの character_set_database はMySQLサーバ設定依存の latin1 とかになり得る)、それから db:migrate を実行した場合、database.yml の encoding が utf8mb4 になっていても、作成されるテーブルの DEFAULT CHARSET は latin1 とかになってしまう。
また例えば、過去に database.yml の encoding を 未指定 or utf8 にした状態で db:create していて、その後 encoding を utf8mb4 に変更してから db:migrate を実行した場合、新たに作成されるテーブルの DEFAULT CHARSET は utf8 のままになる。
この際タチが悪いのは、db:migrate によって作成/更新される db/schema.rb のテーブル定義の DEFAULT CHARSET は、database.yml の encoding 設定に基づいた値になっているということ(その値でテーブルを作成した気になっている)。このため、db/schema.rb に DEFAULT CHARSET=utf8mb4 とあるので安心していたが実際のテーブルは latin1 とか utf8 になっていた、という事態が発生し得る。
いずれの場合も、一旦 db:drop db:create してから db:migrate することで、現在の database.yml の encoding の値を新規作成テーブルの DEFAULT CHARSET に反映できる。
どう気をつけると良いのか
「はじめに結論」の再掲だが、Rails (ActiveRecord) のマイグレーションでは以下に気をつける。
- config/database.yml に
encoding設定があるか?- この設定が存在しない場合、
db:migrateで作成されるテーブルのDEFAULT CHARSETが意図しないものになる可能性がある(Rails 5.1.4 ではutf8になる)
- この設定が存在しない場合、
- データベースが
db:createによって作成されているか?- 他手段(SQL直実行など)で作成した場合、
db:migrateで作成されるテーブルのDEFAULT CHARSETが config/database.yml のencoding設定の値と一致しなくなる
- 他手段(SQL直実行など)で作成した場合、
-
db:create実行後に config/database.yml のencodingを変えたりしていないか?- この状態で
db:drop db:createせずにdb:migrateを実行すると、新規作成されるテーブルのDEFAULT CHARSETは 以前のencodingの設定値になる
- この状態で
あるいは、以下のようにマイグレーションスクリプト側で DEFAULT CHARSET を含むオプションを明記するアプローチもある。こうすれば、上述の留意点が不要になる。
class CreateExamples < ActiveRecord::Migration[5.1]
def change
create_table :examples, options: "DEFAULT CHARSET=utf8mb4" do |t|
...
end
end
end