はじめに結論
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