Help us understand the problem. What is going on with this article?

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

More than 3 years have 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
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away