Open1

MySQL × Docker × 日本語文字列が空白になる問題と対策(UTF-8・locale編)

tech_mwtech_mw

Dockerコンテナ経由でMySQLを使うと、「漢字などのマルチバイト文字」が正しく表示されないことがあります。

この「MySQL × Docker × 日本語」の相性問題は、ベースOSの違い(Debian系 or RedHat系 など) によっても挙動が異なります。

本記事では、具体的な再現例と対策を備忘録としてまとめます。

環境

pc:MacBook Pro(2019)
os:macos Sequoia
docker:Docker version 20.10.6, build 370c289
docker-compose:Docker Compose version 2.0.0-beta.1
docker desktop 利用

GUIツール(Sequel Aceなど)ではなくターミナルから操作したかった理由

  • 下記のような形でコピペしたかったというしょうもない理由です(SequelACEの場合カラム名がコピーできない。Sequel Aceの設定でできるのかも?)
    • カラム名のみ手で書くでも良かったんですが気になった点は潰したかったので検証しました
+------------+--------------+
| ◯◯ | ◯◯ |
+------------+--------------+
| ◯◯ |◯◯◯◯ |
| ◯◯ |◯◯◯◯ |
+------------+--------------+

最も安定した方法

  • GUIツール(例:Sequel Ace)で接続
  • macOSなどホスト側のMySQL(brew経由)で接続
    • mysql -h 127.0.0.1 -P 4306 -u root -p などでDockerコンテナへ接続

再現手順(DockerコンテナでMySQLを使う)

docker-compose.yml
services:
  db:
    image: mysql:8.0
    container_name: mysql_container
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_DATABASE=sample_db
      - MYSQL_USER=development
      - MYSQL_PASSWORD=password
    ports:
      - "4306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
volumes:
  mysql_data:

コンテナ起動

docker compose up -d

mysql接続(Docker経由)

docker exec -it mysql_container mysql -u root -p 

(mysql)DB切替 + 文字列テスト

use sample_db;

-- DUALは仮想テーブル(1行だけのテーブル)
SELECT 
    "商品" AS item_name,
    30     AS quantity
FROM DUAL;

-- 結果:空になる
+-----------+------+
| item_name | quantity |
+-----------+------+
|           |   30 |
+-----------+------+

対処法:ホストMySQLで接続する

brew経由のMySQL起動(macOS)

# Redis用MySQLコンテナのホスト側ポートが3306とバッティングしないよう、4306などにしておく
brew services start mysql

Dockerコンテナ内のMySQLへ接続(macOS)

# ホスト側MySQLクライアントから、Dockerコンテナ内のMySQLにTCP接続
mysql -h 127.0.0.1 -P 4306 -u root -p

補足:MySQL接続時の -h / -P オプションについて

  • -P は「ポート番号」の指定です。
  • -h を省略した場合、MySQLクライアントは localhost と見なし、「UNIXソケット接続」を試みます。つまり -P でポートを指定しても、-h を省略するとポート接続ではなく ソケット接続が優先される ため、DockerコンテナのMySQLにはつながりません。
  • Docker経由のMySQLに接続する場合は -h を明示することが重要です。-h 127.0.0.1 を付けると TCP接続 になるため、-P 4306 が有効になります。

補足:TCP接続とソケット接続の違い(たとえ話)

TCP接続とソケット接続の違い(たとえ話)

🧑‍💻 TCP接続(ネット越しの電話)

「となりの部屋にいる人に話しかけたいけど、ドアが閉まってるから電話で話す」

  • IPアドレスとポート番号を使ってつながる
  • 少し遠回りだけど、ちゃんと声が届く

🏠 ソケット接続(直接声をかける)

「同じ部屋にいる人に、ふつうに話しかける」

  • すごく近くにいるから、ケーブルとかいらない
  • MySQLでは /tmp/mysql.sock みたいなファイルを使って話す
接続のしかた たとえ話 いつ使う?
TCP接続 電話で話す(ネット経由) Dockerコンテナに接続するとき
ソケット接続 直接声をかける(同じ部屋) MacにインストールしたMySQLを使うときなど

正常に表示される例

use sample_db;
SELECT 
    "商品" AS item_name,
    30     AS quantity
FROM DUAL;

-- 結果:OK
+-----------+----------+
| item_name | quantity |
+-----------+----------+
| 商品      |       30 |
+-----------+----------+

チェック1:MySQLの文字コード設定

MySQL接続後に以下を実行:

SHOW VARIABLES LIKE 'character_set%';

例(空白になる場合):

| character_set_client     | latin1     |
| character_set_connection | latin1     |
| character_set_results    | latin1     |

例(空白にならない例):

| character_set_client     | utf8mb4    |
| character_set_connection | utf8mb4    |
| character_set_results    | utf8mb4    |

チェック2:ロケール設定(locale)

docker exec -it mysql_container bash
locale

結果:

LANG=

結果が LANG= だとNG
ロケールが未設定のため、日本語が正しく扱えません。

対処案:optionでの環境変数 + ロケール指定(暫定対応)

MySQLクライアント接続(ただし locale 状態は確認できない)

$ docker exec \                                   
  -e LANG=ja_JP.UTF-8 \
  -e LC_ALL=ja_JP.UTF-8 \
  -it mysql_container \
  mysql -u root -p --default-character-set=utf8mb4

bashで入って locale を確認

docker exec \
  -e LANG=ja_JP.UTF-8 \
  -e LC_ALL=ja_JP.UTF-8 \
  -it mysql_container \
  bash
bash: warning: setlocale: LC_ALL: cannot change locale (ja_JP.UTF-8)
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
...

原因:「ja_JP.UTF-8」ロケールがインストールされていないため

補足:Dockerfileでの対応(例)

  • ※ 今回は割愛していますが、本来は以下のようにDockerfile側でロケール追加するのがベストです。

Debianベースの場合の一例:

FROM mysql:8.0

RUN apt-get update && \
    apt-get install -y locales && \
    locale-gen ja_JP.UTF-8

ENV LANG=ja_JP.UTF-8
ENV LC_ALL=ja_JP.UTF-8

まとめ

チェック項目 内容
character_set_% client, connection, results が utf8mb4
locale(LANG) ja_JP.UTF-8 になっているか
GUIツールやbrewから接続 最も手軽かつ確実に日本語を扱える
ロケール未設定時の挙動 日本語文字列が空白になる(MySQL上の仕様)