MySQL8とRailsの組み合わせでcollationの罠にハマった話
こんにちは、Yuanです!Railsのアプリケーションをいじってます。
今日は、RailsアプリケーションのデータベースをMySQL5.7からMySQL8.0へメジャーアップデートしたときに、collation(照合順序)の設定で罠にハマったため、その対策と原因についてまとめます。
まとめから言うと?
下記の今北産業(三行要約)です。サクッと解決策を知りたい方はこちらで十分だと思います。
- Railsの
database.yml
にcharset
とcollation
の設定をちゃんと書こうね - MySQL5.7とMySQL8.0は文字コード
utf8mb4
に対応するデフォルトのcollationが違うよ(utf8mb4_general_ci
→utf8mb4_0900_ai_ci
) - mysqldの設定に
character-set-server
とcollation-server
を入れてもrails db:create
するときに意味がないよ
そもそも
今回、MySQLのアップデート対象のRailsアプリケーションは、database.yml
にcharset
もcollation
も設定されていませんでした。
そのため、このアプリケーションのデータベースは、MySQL5.7のデフォルト文字コードであるutf8mb4
に対応した照合順序であるutf8mb4_general_ci
が自動的に使われて続けている状態です。
しかし、MySQLを5.7から8.0にメジャーアップデートする必要にあたって、一つの問題点があります。
それは、MySQL8.0では文字コードutf8mb4
にデフォルトで対応する照合順序がutf8mb4_0900_ai_ci
に変わったと言うことです。
この照合順序が変わると、文字を使ったソートの順番が変わったり、異なる文字を同じ文字とみなす(🍣=🍺問題)ルールが変わります[1]。
そのため、このアプリケーションのMySQLのアップデートでは照合順序を維持する必要がありました。
何に困った?
ローカルの開発環境をMySQL8.0で新たに作ったときに問題が発生しました。
それは、本番環境とローカル環境で照合順序(collation)が異なってしまう問題です。
MySQLのメジャーアップデートは、すでにあるデータベースに対しては設定済みの照合順序を維持してくれるため、アップデートに関して特に気にする必要はありません。
しかし、開発環境などで新規にデータベースを作ったときに、MySQLのデフォルトの照合順序が本番環境ズレてしまいます。
このズレは、「本番環境だけで発生するバグ」の原因となることがあるため、防がなければなりません。
どうやったら対処できる?
とても簡単です。Railsアプリケーションのdatabase.yml
にcollation: utf8mb4_general_ci
を追加すれば良いだけです。
default: &default
adapter: mysql2
encoding: utf8mb4
charset: utf8mb4
collation: utf8mb4_general_ci
これにより、MySQL8.0で新規にデーターベースを作るときも、照合順序をutf8mb4_general_ci
に固定することができます。
Q&A
mysqldの起動のパラメータやmy.cnfで照合順序を固定する方法ではダメなの?
先に結論から言うとダメです。
database.yml
にcollation
を指定してください。
この質問は、mysqldの起動パラメータやmy.cnfに下記を設定することを指していますが、後述のRailsの実装による理由のためrails db:create
でデータベースを作るときに反映されません。
mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_general_ci
なぜ、これらが意味がないかというとrails db:create
のコードを読むと下記のように書かれているためです。
def create_database(name, options = {})
if options[:collation]
execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}"
elsif options[:charset]
execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset])}"
elsif row_format_dynamic_by_default?
execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET `utf8mb4`"
else
raise "Configure a supported :charset and ensure innodb_large_prefix is enabled to support indexes on varchar(255) string columns."
end
end
このコードのとおり、app_development
データベースに対してcharset
もcollation
が設定されていない場合は、CREATE DATABASE app_development DEFAULT CHARACTER SET utf8mb4
が実行されます。
MySQLは、データーベースを作るときに文字コードのみが指定されている場合は、その文字コードに対応したMySQL内部のデフォルトの照合順序を使います[2]。
すなわち、起動のパラメータやmy.cnfで設定されたものを無視して、MySQLに内蔵されたマッピングテーブルを元に決定されます(このテーブルはshow collation
で確認できます)。
以上から、rails db:create
でデータベースを作るときは、mysqldの起動のパラメータやmy.cnfで照合順序を固定する方法は使えません。
他にも、MySQLのアップデートなどでcollationが変わることを良しとしない場合は、database.ymlにcollationを書いておいた方が良いことがわかります。
まとめ
最初にも書いているとおりに、下記を守れば開発環境と本番環境で照合順序の違いが発生する可能性を減らすことができます。
- Railsの
database.yml
にcharset
とcollation
の設定をちゃんと書こうね - MySQL5.7とMySQL8.0は文字コード
utf8mb4
に対応するデフォルトのcollationが違うよ(utf8mb4_general_ci
→utf8mb4_0900_ai_ci
) - mysqldの設定に
character-set-server
とcollation-server
を入れてもrails db:create
するときに意味がないよ
最後に
この問題に躓いてから、原因を特定できるまで約一日を使ってしまいました😭。
ただ、文字コードと照合順序に関して詳しくなれたのでよしとします!
もし、MySQL8アップデートを計画されている方にこの記事が役立つことがあれば幸いです。
質問がありましたら、コメント欄にお気軽にどうぞ!
Discussion
encodingとcharsetは別物だとわかったため、記事を修正しました。