Edited at

Rails (ActiveRecord) マイグレーションで MySQL テーブル作成時の文字コード (DEFAULT CHARSET) はどのように設定されるのか

More than 1 year has passed since last update.


はじめに結論

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 設定の値と一致しなくなる




  • db:create 実行後に config/database.yml の encoding を変えたりしていないか?


    • この状態で db:drop db:create せずに db:migrate を実行すると、新規作成されるテーブルの DEFAULT CHARSET は 以前の encoding の設定値になる



あるいは、以下のようにマイグレーションスクリプト側で DEFAULT CHARSET を含むオプションを明記するアプローチもある。こうすれば、上述の留意点が不要になる。


db/migrate/yyyymmddhhmmss_create_examples.rb

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 を設定するようだ。

上記は 5.1.4 (2018/01/17 時点での最新版) での実装だが、例えば Qiitaのこのエントリ を見ると 4.2系でも同様の実装のようだ。


どのようにハマり得るのか

例えば、事前にSQL直実行でデータベースを作成し(この時点で作成したデータベースの character_set_database はMySQLサーバ設定依存の latin1 とかになり得る)、それから db:migrate を実行した場合、database.yml の encodingutf8mb4 になっていても、作成されるテーブルの DEFAULT CHARSETlatin1 とかになってしまう。

また例えば、過去に database.yml の encoding を 未指定 or utf8 にした状態で db:create していて、その後 encodingutf8mb4 に変更してから db:migrate を実行した場合、新たに作成されるテーブルの DEFAULT CHARSETutf8 のままになる。

この際タチが悪いのは、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 設定の値と一致しなくなる




  • db:create 実行後に config/database.yml の encoding を変えたりしていないか?


    • この状態で db:drop db:create せずに db:migrate を実行すると、新規作成されるテーブルの DEFAULT CHARSET は 以前の encoding の設定値になる



あるいは、以下のようにマイグレーションスクリプト側で DEFAULT CHARSET を含むオプションを明記するアプローチもある。こうすれば、上述の留意点が不要になる。


db/migrate/yyyymmddhhmmss_create_examples.rb

class CreateExamples < ActiveRecord::Migration[5.1]

def change
create_table :examples, options: "DEFAULT CHARSET=utf8mb4" do |t|
...
end
end
end