EC-CUBE4.3 Doctrine MySQL PHP データベース バージョンアップ

EC-CUBE 4.0系から引き継いだ utf8_general_ci のDBを、4.3の標準である utf8mb4_bin にまとめて変換するのは、MySQL 5.7でも ALTER TABLE ... CONVERT TO CHARACTER SET を全テーブルに対して実行するだけで済みます。ただし、事前に information_schema で3つの上限を計算しておくこと、変換後にMySQLが自動で広げたTEXT型にEntityの宣言を合わせること、この2点を忘れると、変換そのものは成功しても、あとで schema:update が既存データを切り詰める危険なALTERを出すようになります。

変換前にinformation_schemaで見る3つの上限

utf8は1文字最大3バイト、utf8mb4は4バイトなので、文字列列のバイト数が4/3倍になります。MySQL 5.7で超えてしまう可能性がある上限は次の3つです。

上限 見る場所
行フォーマット Dynamic / Compressed なら索引 3072 バイト、Compact / Redundant なら 767 バイト information_schema.tables.row_format
索引の長さ 3072 バイト(innodb_large_prefix=ON 時) information_schema.statistics の列を文字数×4 で合計
行の最大サイズ 65535 バイト information_schema.columns の varchar/char を長さ×4+2 で合計

索引の長さは、索引を構成する列ごとに文字数×4(sub_partがあればそれ×4)と数値型のバイト数を足せば見積もれます。

SELECT s.table_name, s.index_name,
  SUM(CASE
    WHEN c.data_type IN ('varchar','char') THEN COALESCE(s.sub_part, c.character_maximum_length) * 4
    WHEN c.data_type LIKE '%text' THEN COALESCE(s.sub_part, 0) * 4
    WHEN c.data_type = 'bigint' THEN 8
    WHEN c.data_type = 'int' THEN 4
    WHEN c.data_type = 'smallint' THEN 2
    WHEN c.data_type = 'datetime' THEN 5
    ELSE 8 END) AS mb4_key_bytes
FROM information_schema.statistics s
JOIN information_schema.columns c
  ON c.table_schema = s.table_schema AND c.table_name = s.table_name AND c.column_name = s.column_name
WHERE s.table_schema = 'eccube_db'
GROUP BY 1, 2 ORDER BY 3 DESC LIMIT 10;

行サイズは同じ要領で character_maximum_length * 4 + 2 をテーブルごとに合計します。EC-CUBEの標準スキーマでは索引の最大は varchar(255) 単独の1020バイト前後ですが、行サイズは文字列列をカスタマイズで増やした dtb_customerdtb_order が5万バイト台まで来るので、65535に近いテーブルは、先にvarcharをtextに変更してから変換します。「767バイト制限があるので varchar(255) に索引が作れない」という情報を見かけますが、行フォーマットがDynamicで innodb_large_prefix=ON(5.7の既定)であればこの制限は適用されません。

ALTER文はinformation_schemaから生成する

テーブル名を手で並べると漏れるので、SQLで生成します。既にutf8mb4_binのテーブルは除外します。

SELECT CONCAT('ALTER TABLE `', table_name, '` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;')
FROM information_schema.tables
WHERE table_schema = 'eccube_db' AND table_collation <> 'utf8mb4_bin'
ORDER BY table_name;

先頭に ALTER DATABASE `eccube_db` CHARACTER SET utf8mb4 COLLATE utf8mb4_bin; を付けて1つのSQLファイルにし、フルダンプを取ってから流します。CONVERT TO はテーブルを作り直すので、GB級のログテーブルを含む90テーブル弱で1分半かかりました。実行中は、変換しているテーブルへの書き込みがブロックされます。

TINYTEXTが自動でTEXTになり、schema:updateが縮めようとする

ここが本題です。CONVERT TO CHARACTER SET utf8mb4 は「格納できる文字数を減らさない」ために、TEXT系の型を自動で1段広げます。TINYTEXT(255バイト)はutf8で85文字入りますが、utf8mb4では63文字になるので、MySQLはTINYTEXTをTEXTに、TEXTをMEDIUMTEXTに変えます。実測では60列以上が変わりました。

問題はDoctrine側です。Entityで type="text", length=255 と宣言していると、DoctrineはMySQLではTINYTEXTを期待するので、変換後に --dump-sql を見るとこう出ます。

ALTER TABLE dtb_product CHANGE sub_title1 sub_title1 TINYTEXT DEFAULT NULL, CHANGE sub_comment1 sub_comment1 TEXT DEFAULT NULL, ...

これを --force で流すと、日本語63文字を超えるデータが入っている列で、データの切り詰めかエラーが起きます。対処は、Entityの length を変換後の実型に合わせることです。Doctrineは length をバイト数として見て、255以下ならTINYTEXT、65535以下ならTEXT、16777215以下ならMEDIUMTEXTを選ぶので、length=25565535 に、length=65532 のような値を 16777215 に書き換えます。coreのEntityで type="text"length が無い列は、TEXTとMEDIUMTEXTを区別しないので差分になりません。

_binにする前に大文字小文字の混在を数える

utf8mb4_bin は大文字と小文字を区別します。utf8_general_ci では同じ値だった会員メールアドレスや管理者IDが、変換後は別の値になり、ログインに使う = 比較が通らなくなります。変換前にこの2つを数えて、0件であることを確認してから進めます。

SELECT COUNT(*) FROM dtb_customer WHERE email <> LOWER(email);
SELECT COUNT(*) FROM dtb_member WHERE login_id <> LOWER(login_id);

0件でなければ、変換前に小文字へ揃えるか utf8mb4_general_ci を選ぶかを決めます。管理画面の英字検索が大小を区別するようになりますが、4.3の新規インストールと同じ挙動です。接続側の DATABASE_CHARSET は4.3の .env.distutf8 のままなので、変える必要はありません。

日本発!ECオープンプラットフォーム「EC-CUBE」 EC-CUBEゴールドパートナー EC-CUBEは株式会社イーシーキューブの商標です

EC-CUBEに関するお問い合わせ


    [重要]現在公式にセキュリティサポートが切れていないPHPは8.1以上、MySQLは8.0以上で、対応しているEC-CUBEバージョンは4.2以上です。古いEC-CUBEを使っている方は適切なタイミングでバージョンアップをご検討ください。

    EC-CUBEバージョンアップ