--lower-case-table-names=1 を足した次の起動から、MySQL が上がらなくなります。ログの終わりはこの 4 行です。
[ERROR] [MY-011087] [Server] Different lower_case_table_names settings for server ('1') and data dictionary ('0').
[ERROR] [MY-010020] [Server] Data Dictionary initialization failed.
[ERROR] [MY-010119] [Server] Aborting
[System] [MY-010910] [Server] /usr/sbin/mysqld: Shutdown complete (mysqld 8.4.11) MySQL Community Server - GPL.
同じ検索で当たる表示ゆれは次のとおりです。
Different lower_case_table_names settings for server ('0') and data dictionary ('1')(数字が逆の形。1 で初期化したデータディレクトリを、オプションを付けずに起動したとき)[ERROR] [MY-010020] [Server] Data Dictionary initialization failed.(同じ停止の 2 行目。こちらだけを拾って検索することがあります)Variable 'lower_case_table_names' is a read only variable(SET GLOBALで変えようとしたとき)lower_case_table_names was set to 2, even though your the file system '/var/lib/mysql/' is case sensitive.(=2を渡したときの警告。your theは MySQL が出す原文のまま)Different lower_case_table_names settings for server ('1') and data dictionary ('2')(データディレクトリをバインドマウントにしている場合。後述)Table 'app.users' doesn't exist(MySQL 5.7 で同じ変更をしたときに出る形。後述)
この値は、データディレクトリを初期化したときに決まっています。 起動オプションを書き換えても変わらないので、変えるにはダンプを取り、データディレクトリを空にして --lower-case-table-names=1 を付けて初期化し直し、そこへ戻します。
1 にすること自体は MySQL 自身が勧めています。8.4 のマニュアルにこうあります。
Exception: If you are using
InnoDBtables and you are trying to avoid these data transfer problems, you should uselower_case_table_names=1on all platforms to force names to be converted to lowercase.
止まるのは、すでに初期化されたデータディレクトリへ後から渡した場合だけです。
起動しないことより、黙って進むことのほうが危ない
この記事で測った 2 つは、どちらもエラーを出しません。先にここだけ読んでください。
- MySQL 5.7 で同じ変更をすると、サーバは起動します。 そのあと大文字混じりのテーブルが引けなくなり、
SHOW TABLESには名前が残ったままSELECTだけが失敗します。lower_case_table_namesに触れる警告は 1 行も出ません。8.0.11 以降が起動を止めるのは、この状態を作らせないためです。 - 世に出回っている「ダンプして初期化やり直し」は、大小だけが違う名前があると終了コード 0 のまま行を落とします。
app.Usersとapp.usersは復元後に 1 つになり、片方のデータが消えます。エラーも警告も出ません。
だから下の手順は、衝突の点検から始まります。
直し方:ダンプを取り、初期化からやり直す
手順は 4 つ。1 番目を飛ばすと、大小だけが違う名前があった場合に復元が終了コード 0 で終わったまま行が消えます(後述)。
- 大小だけが違うデータベース名・テーブル名を数える
mysqldumpでダンプを取る- データディレクトリを空にして、
--lower-case-table-names=1を付けて初期化する - ダンプを戻す
まず、いま起動できる状態(--lower-case-table-names を外した状態)でサーバを立てて数えます。テーブル名から。
SELECT LOWER(table_schema) AS s, LOWER(table_name) AS t, COUNT(*) AS n,
GROUP_CONCAT(CONCAT(table_schema, '.', table_name)) AS names
FROM information_schema.tables
WHERE table_schema NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')
GROUP BY 1, 2 HAVING COUNT(*) > 1;
データベース名も同じように数えます。
SELECT LOWER(schema_name) AS s, COUNT(*) AS n, GROUP_CONCAT(schema_name) AS names
FROM information_schema.schemata
WHERE schema_name NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')
GROUP BY 1 HAVING COUNT(*) > 1;
どちらも 0 行なら先へ進めます。行が出たら、どちらのデータを残すかを決めて、片方を空いている別の名前へ変えてから進めてください。lower_case_table_names=0 のあいだは RENAME TABLE が通ります。ただし寄せることはできません。
mysql> RENAME TABLE app.Users TO app.users;
ERROR 1050 (42S01): Table 'users' already exists
2 つは別々のテーブルで、中身も別です。空いている名前(app.users_legacy など)へ変えれば改名は通り、両方が復元後も残ります。
次にダンプを取ります。ホスト側のファイルに出してください。 コンテナの中に置くと、次の手順でコンテナごと消えます。
docker compose exec -T db mysqldump -uroot -p --all-databases --routines --events --triggers > dump.sql
--all-databases には mysql スキーマも入るので、アカウントと権限も一緒に戻ります。アプリ用のユーザに GRANT SELECT, INSERT ON app.* を与えた状態で往復させたところ、復元後に SHOW GRANTS が同じ 2 行を返し、そのユーザで接続してクエリも通りました。
この手順が成立するのは、同じ版の MySQL へ戻す単体サーバの場合です。 mysql スキーマごと書き戻すので、版をまたぐアップグレードを兼ねたり、レプリケーションのプライマリで実行したりする用途には使えません。書き込みも止めてください。ここで使っている mysqldump はデータベース単位のロックしか取らないので、動いているアプリが複数のスキーマへ書いている最中に取ると、スキーマをまたいだ整合性が崩れます。
打つ前にダンプの末尾を見てください。
tail -1 dump.sql
-- Dump completed on 2026-08-08 13:13:38
この行があれば最後まで書けています。容量不足などで途中で切れたダンプはこの行を持ちません。次はボリュームを消す操作なので、ここで確かめておくと戻れます。
データディレクトリを空にして、フラグ付きで初期化し直します。compose.yml は次の形です。
services:
db:
image: mysql:8.4.11
environment:
MYSQL_ROOT_PASSWORD: root
command: --lower-case-table-names=1
volumes:
- db-data:/var/lib/mysql
volumes:
db-data:
この compose にサービスが 1 つしか無いなら、down -v で消せます。
docker compose down -v
docker compose up -d
MySQL 以外のサービスが同じ compose にいるなら、down -v は使わないでください。 -v が消すのはそのファイルの volumes: に宣言された名前付きボリューム全部で、直したいサービスのぶんだけではありません。db と cache の 2 サービスを立てて down -v を打つと、cache 側のボリュームも消えました。MySQL のぶんだけを消す形は次のとおりです。
docker compose rm -sf db
docker volume rm <プロジェクト名>_db-data
docker compose up -d db
プロジェクト名はディレクトリ名か -p で決まります。実際の名前は docker volume ls で確認してください。どちらの経路でも、ボリュームが空になったので、次の起動でデータディレクトリが lower_case_table_names=1 として初期化されます。
戻すのはダンプ 1 本です。
docker compose exec -T db mysql -uroot -p < dump.sql
docker compose exec db mysql -uroot -p -e 'FLUSH PRIVILEGES'
FLUSH PRIVILEGES は省けません。 --all-databases のダンプは mysql スキーマの権限テーブルを直接書き戻しますが、サーバがメモリに読み込んでいる権限は入れ替わりません。打たずにアプリ用ユーザで接続すると、mysql.user に行があるのに ERROR 1045 (28000): Access denied for user 'appuser'@'127.0.0.1' (using password: YES) になります(mysqldump の出力に FLUSH PRIVILEGES は含まれていません)。
確認は 2 つ。設定値と、大文字混じりの名前で引けることです。
mysql> SELECT @@lower_case_table_names;
@@lower_case_table_names
1
mysql> SELECT COUNT(*) FROM app.Users;
COUNT(*)
1
ディスク上のファイル名は users.ibd になり、app.Users でも app.users でも同じテーブルに当たります。lower_case_table_names=1 は保存時に小文字化し、比較のときも小文字化するからです。
初期化をやり直さない別解もあります。テーブル名を RENAME TABLE で小文字に揃えるだけなら、いま動いているサーバのまま通ります。ただし lower_case_table_names は 0 のままなので、SELECT ... FROM Users と書いたクエリは引き続き通りません。アプリ側の SQL を全部小文字へ直せるならこちらのほうが軽く、直せないなら初期化やり直しになります。
なぜ MySQL 8.0.11 以降で出るのか:識別子の並び順が初期化時に決まる
8.4 のマニュアルは 2 文で言い切っています。
lower_case_table_namescan only be configured when initializing the server. Changing thelower_case_table_namessetting after the server is initialized is prohibited.
禁止が入ったのは 8.0.11、つまり MySQL 8.0 の GA です。8.0.11 のリリースノートの Bugs Fixed に、そのまま書いてあります(Bug #27309094 / Bug #89035)。
It is now prohibited to start the server with a
lower_case_table_namessetting that is different from the setting used when the server was initialized.
同じ段落が理由も書いています。
collations used by data dictionary table fields are determined by the setting defined when the server is initialized, and restarting with a different setting would introduce inconsistencies with respect to how identifiers are ordered and compared
データディクショナリの表そのものが、初期化時の設定に合わせた照合順序で作られています。 別の設定で起動すると、識別子の並び方と比べ方が食い違う。起動時にその不一致を検出して止めているので、接続元にもテーブルの中身にも関係なく、起動の段で終わります。エラー文の data dictionary ('0') は、この照合が前提にしている初期化時の設定です。
実行中のサーバで変える道はありません。
mysql> SET GLOBAL lower_case_table_names = 1;
ERROR 1238 (HY000): Variable 'lower_case_table_names' is a read only variable
mysql> SET PERSIST lower_case_table_names = 1;
ERROR 1238 (HY000): Variable 'lower_case_table_names' is a read only variable
同じ操作(既定の 0 で初期化してから --lower-case-table-names=1 で起動)を、イメージの版だけ変えた結果が次のとおりです。
| イメージ | 起動 | ログ |
|---|---|---|
mysql:5.7.44 | する | lower_case_table_names に触れる行が 1 つも無い |
mysql:8.0.4(GA 前の RC) | する | 同じく 1 行も無い |
mysql:8.0.11(8.0 の GA) | しない | MY-011087 → MY-010119 |
mysql:8.4.11 | しない | MY-011087 → MY-010020 → MY-010119 |
mysql:9.7.2 | しない | 同じ |
mysql:26.7.0 | しない | 同じ |
境界は 8.0 の中、GA の 8.0.11 にあります。8.0.11 より前の 8.0 は開発版(DMR / RC)で、Docker 公式イメージで引けるのも 8.0.4 と 8.0.11 から先です。リリース版の MySQL 8.0 を使っているなら、この禁止は最初から入っています。上の表の 8.0.11 以降では、文言もエラー番号も同じでした。
MySQL 5.7 で同じ操作をしたときの挙動
5.7 は起動します。起動したあとの状態はこうなります。
mysql> SHOW TABLES FROM app;
Users
mysql> SELECT * FROM app.Users;
ERROR 1146 (42S02): Table 'app.users' doesn't exist
mysql> SELECT * FROM app.users;
ERROR 1146 (42S02): Table 'app.users' doesn't exist
ディスク上のファイルは Users.frm と Users.ibd のままで、SHOW TABLES にもその名前が出ます。名前で引くときだけ小文字化されるので、どちらの綴りで書いても app.users を探して見つかりません。エラー文が返す名前も、入力した綴りではなく小文字化したあとの名前です。データは消えていないが到達できない、という状態になります。
lower_case_table_names に触れる警告・エラーは、起動ログに 1 行も出ません(TIMESTAMP の非推奨や TLS まわりの警告は出ますが、この変更とは無関係です)。この状態のまま mysqldump を取ろうとしても、終了コード 2 で失敗します。
mysqldump: Got error: 1146: Table 'app.users' doesn't exist when using LOCK TABLES
5.7 のマニュアルは、この変更に前工程を書いていました。
If you plan to set the
lower_case_table_namessystem variable to 1 on Unix, you must first convert your old database and table names to lowercase before stopping mysqld and restarting it with the new variable setting.
8.0 はこの指示を禁止に置き換えています。MY-011087 で止まるのは、前工程を済ませていないデータディレクトリを起動前に弾いているためで、5.7 のときに黙って起きていた到達不能を起動時のエラーに変えたものです。
5.7 側でここに入ってしまった場合、lower_case_table_names を 0 に戻せば元どおり読めます。
この節で測ったのは mysql:5.7.44(5.7 系の最終版)1 点です。5.7 系の他のパッチ版は測っていません。MariaDB もこの記事の対象ではありません(MY-011087 は MySQL 8.0 のデータディクショナリを前提にしたエラーです)。
復元で潰れる名前:大小だけが違うデータベース名・テーブル名
app.Users(1 行)と app.users(1 行)を両方持つ状態から、上の手順をそのまま通した結果です。
$ mysql -uroot -p < dump.sql
$ mysql -uroot -p -e 'SHOW TABLES FROM app'
users
$ mysql -uroot -p -e 'SELECT * FROM app.users'
id
2
app.Users に入れていた行はありません。復元の終了コードは 0 です。
理由はダンプの中身にあります。mysqldump は各テーブルの前に DROP TABLE IF EXISTS を書きます。
USE `app`;
DROP TABLE IF EXISTS `Users`;
CREATE TABLE `Users` (
...
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
lower_case_table_names=1 のサーバでは Users と users が同じ名前になるので、2 つ目の DROP TABLE IF EXISTS が 1 つ目で作ったテーブルを落とします。存在しないものを落としても成功する構文なので、エラーになりません。残るのはダンプファイルの後ろにある側です。
どちらが後ろに来るかは mysqldump の出力順で決まります。作成順ではありません——users を先に作って Users を後に作っても、ダンプは Users・users の順で出ました(2 例とも名前順に見えます)。ただし出力順を保証する記述は上流に見つけられなかったので、どちらが残るかを当てにしないでください。 潰すのは手順 1 です。
データベース名でも同じ形で潰れます。
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `App` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */;
USE `App`;
app が既にあるので CREATE DATABASE IF NOT EXISTS は何もせず、続く USE App は app を選びます。app と App に別々のテーブルを置いていた場合、2 つのデータベースが app 1 つに合流します。これも終了コード 0 で終わります。
だから手順の 1 番目は数えることです。0 行であることを確かめてからダンプを取れば、この経路で落ちるものはありません。
macOS / Windows の既定値を Linux へ持ち込んだ場合
既定値はプラットフォームで違います。
On Unix, the default value of
lower_case_table_namesis 0. On Windows, the default value is 1. On macOS, the default value is 2.
この差から、macOS や Windows に直接入れた MySQL では大小を区別せずに引けていたクエリが、Linux のサーバやコンテナでは通らなくなります(本記事が実測したのは Linux 側だけで、macOS / Windows のサーバは動かしていません。上の既定値はマニュアルの記述です)。--lower-case-table-names=1 を足す場面はここです。
=2 をそのまま持ち込んでも移りません。大小を区別するファイルシステムでは、MySQL が値を落とします。
[Warning] [MY-010160] [Server] lower_case_table_names was set to 2, even though your the file system '/var/lib/mysql/' is case sensitive. Now setting lower_case_table_names to 0 to avoid future problems.
名前付きボリュームを使っているなら、初期化時も起動時も 0 に落ちます。=2 だけを書いているあいだは不一致になりません。データディクショナリに入るのは 0 で、そのあと 1 に変えた時点で MY-011087 です。
エラー文が出す数字は、書いた値ではなく落としたあとの値です。1 で初期化したデータディレクトリに --lower-case-table-names=2 を渡すと、2 は 0 になってから突き合わせられるので server ('0') and data dictionary ('1') と出ます。指定した 2 はどこにも現れません。 my.cnf に書いた数字とエラー文の数字を突き合わせても合わないので、そこで診断が止まります。
バインドマウントを使っていると、=2 はそのまま入る
上の「0 に落ちる」は、データディレクトリのファイルシステムが大小を区別する場合の話です。ホストのディレクトリを /var/lib/mysql にバインドマウントしていると、そうならないことがあります。
Windows のホストでバインドマウントを試したところ、コンテナの中から見たそのディレクトリは大小を区別しませんでした。--lower-case-table-names=2 での初期化は MY-010160 を 1 行も出さずに成功し、データディクショナリには 2 が入ります。
mysql> SELECT @@lower_case_table_names;
@@lower_case_table_names
2
この状態から 1 に変えると、3 つ目の数字の組が出ます。
[ERROR] [MY-011087] [Server] Different lower_case_table_names settings for server ('1') and data dictionary ('2').
同じ compose.yml でも、名前付きボリュームかバインドマウントかで結果が変わります。 自分がどちらにいるかは SELECT @@lower_case_table_names で分かります。0 が返るなら大小を区別するファイルシステム、2 が返るならバインドマウント側です。直し方はどちらも同じで、この記事の 4 手順になります。
1 で初期化したあとは、そのオプションが必須になる
--lower-case-table-names=1 で初期化したデータディレクトリを、オプションを付けずに起動すると数字が逆の不一致で止まります。
[ERROR] [MY-011087] [Server] Different lower_case_table_names settings for server ('0') and data dictionary ('1').
サーバに 1 が渡らなくなる経路はどれも同じ結果になります。command 行をまとめて消した、my.cnf のマウントを外した、設定ファイルが読まれなくなった——どれも、サーバ側の値が既定の 0 に戻るという 1 点で同じです(実測したのはオプションを渡さない形です)。1 を選んだ時点で、その指定はデータディレクトリと一緒に持ち歩くものになります。
切り分け
- 数字が
server ('0') and data dictionary ('1')になっている:向きが逆で、1 で初期化したデータディレクトリをオプション無しで起動しています。--lower-case-table-names=1を付け直せば起動します。データディレクトリを作り直す必要はありません。 - サーバは起動していて
Table 'xxx.yyy' doesn't existだけが出る:MySQL 5.7 でこの変更をした状態です。SHOW TABLESに名前が残っていてSELECTが通らないなら、値を 0 へ戻すと読めます。 lower_case_table_names was set to 2 ...の警告だけが出て起動している:値は 0 に落ちています。止まってはいないので、この警告だけならデータディレクトリは無事です。この警告が出ないまま=2が通っているなら、データディレクトリが大小を区別しないファイルシステム(バインドマウントなど)に載っています。- 数字が
server ('1') and data dictionary ('2')になっている:=2で初期化されたデータディレクトリに1を渡しています。上の 4 手順で直ります。バインドマウントのままでも--lower-case-table-names=1での初期化は通り、@@lower_case_table_namesは 1 を返します。 - 列名やインデックス名の大小:この変数の対象ではありません。8.4 のマニュアルは
Partition, subpartition, column, index, stored routine, event, and resource group names are not case-sensitive on any platform, nor are column aliases.と書いています。対象はデータベース名とテーブル名です。 - マネージドサービスで変えられない:RDS では、MySQL 8.0 / 8.4 のパラメータグループでこの値を変更できないとされています。拒否はサーバが起動する前にサービス側で起きるので、
MY-011087は出ません。本記事はマネージドサービスを実測していないので、対処は各サービスの移行手順を確認してください(インスタンスを作り直す側の話になります)。 - 別の変数名で
unknown variableが出ている:この記事の経路ではありません。8.4 で削除された変数を指定しているなら、サーバはオプション解析の段で止まります。
検証環境
mysql:8.4.11、ネットワーク遮断(イメージの pull のみ)- 再現:既定(0)で初期化してテーブルを 1 つ入れたデータディレクトリに
docker-entrypoint.sh mysqld --lower-case-table-names=1を渡すと、Different lower_case_table_names settings for server ('1') and data dictionary ('0')を出して終了コード 1 - 修正:同じイメージのまま、衝突の点検 →
mysqldump --all-databases --routines --events --triggers→ データディレクトリを空にして--lower-case-table-names=1で初期化 → 復元 →FLUSH PRIVILEGES。SELECT @@lower_case_table_namesが 1 を返し、SELECT COUNT(*) FROM app.Usersが終了コード 0
再現から修正までは errfix の検証ハーネスが機械的に確認しています。版の対照(5.7.44 は起動し、8.0.46 / 8.4.11 / 9.7.2 / 26.7.0 は MY-011087)、5.7 で起動したあとに SHOW TABLES へ Users が残り両方の綴りで Table 'app.users' doesn't exist になること・その間ログに警告が出ないこと・mysqldump が終了コード 2 で失敗すること・値を 0 に戻すと読めること、SET GLOBAL と SET PERSIST が ERROR 1238 になること、=2 が大小を区別するファイルシステムで 0 に落ちること、数字が逆の不一致(server ('0') and data dictionary ('1'))、大小だけが違う名前を含むダンプの復元が終了コード 0 のまま片方を落とすこと(テーブル名とデータベース名の両方)は、いずれも同じ手順で実測しています。
前工程についても測りました。衝突している名前へ RENAME TABLE で寄せると ERROR 1050、空いている名前へなら通ること。改名で衝突を解いてから手順を通すと検出クエリが 0 行になり、復元後に users(2 行目のデータ)と users_legacy(1 行目のデータ)が両方残ること。--all-databases のダンプがアプリ用アカウントを戻し、SHOW GRANTS が往復前と同じ 2 行を返し、そのユーザで接続して SELECT が通ること。FLUSH PRIVILEGES を打たないと、mysql.user に行があるのに ERROR 1045 で接続できないこと。
本文に載せた 4 手順は、載せた形のままホスト側から通しました(docker compose exec -T db mysqldump ... > dump.sql → ボリューム削除 → --lower-case-table-names=1 で初期化 → docker compose exec -T db mysql ... < dump.sql → FLUSH PRIVILEGES)。ダンプがホストに残ること、末尾に -- Dump completed on ... が付くこと、down -v が同じ compose の別サービスのボリュームも消すこと、docker compose rm -sf db + docker volume rm なら MySQL のぶんだけ消えることも実測です。
=2 まわりも測り直しました。Windows ホストのバインドマウントはコンテナから見て大小を区別せず、--lower-case-table-names=2 の初期化が MY-010160 を出さずに通り、@@lower_case_table_names が 2 を返し、そのデータディレクトリに 1 を渡すと server ('1') and data dictionary ('2') になること。同じバインドマウントでも 1 での初期化は通ること。
mysqldump と mysql はコンテナの中で打った形で載せています。ホストから流し込むなら docker compose exec -T db を前に付けてください(標準入力を渡すので -T が要ります)。docker compose down -v / up -d の 2 行は compose を使う読者向けの書き方で、検証で実際に走らせたのは同じことをするコンテナ内の操作(データディレクトリを空にして、--lower-case-table-names=1 を付けた entrypoint で初期化しなおす)です。
仕様の記述は MySQL 8.4 と 5.7 のリファレンスマニュアル「Identifier Case Sensitivity」によります。macOS / Windows のサーバ、マネージドサービス側の挙動、実データでのダンプ・復元の所要時間は実測していません(検証したのは Linux のコンテナで、テーブルが数個のデータディレクトリです)。