Docker 部署后 MySQL 中文变问号排查实录

Docker 部署后 MySQL 中文变问号排查实录

环境:MySQL 8.0 容器 + Flask + docker-compose,种子数据来自 db.sql

1. 现象

示例

SYS_SITE_CONFIG 等表中文全变 ?,但 © 正常:

1
SELECT * FROM SYS_SITE_CONFIG LIMIT 3;
1
2
3
4
5
6
7
+---------------------+---------------------------------------------+---------------+
| config_key | config_value | description |
+---------------------+---------------------------------------------+---------------+
| sys.site.copyright | Copyright © 2026 ???? All Rights Reserved. | ????????? |
| sys.site.loginDesc | ????????????????????????????????????? | ????????? |
| sys.site.loginTitle | ?????? | ????????????? |
+---------------------+---------------------------------------------+---------------+

2. 初步怀疑:表/库字符集

1
2
3
SHOW CREATE TABLE SYS_SITE_CONFIG\G
SELECT HEX(LEFT(config_value,12)) AS hex_head FROM SYS_SITE_CONFIG WHERE config_key='sys.site.loginDesc'\G
SELECT @@character_set_database;
1
2
3
4
5
6
7
Create Table: CREATE TABLE `SYS_SITE_CONFIG` (...) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
hex_head: 3F3F3F3F3F3F3F3F3F3F3F3F
+--------------------------+
| @@character_set_database |
+--------------------------+
| utf8mb4 |
+--------------------------+

结论:表和库都是 utf8mb4,但 HEX 全是 3F(ASCII ?),说明中文在写入时就被物理替换,不是显示问题。

db.sql 本身没问题:

1
2
3
4
file -i db.sql
# db.sql: text/plain; charset=utf-8
grep -c "集打卡" db.sql
# 1

3. 文件字节是否正确

1
2
3
4
5
6
grep "sys.site.loginDesc" db.sql | od -An -tx1 | head
# 28 27 73 79 73 2e ... e9 9b 86 e6 89 93 e5 8d a1 e3 80 81 ...
# e9 9b 86 e6 89 93 e5 8d a1 = "集打卡" 的正确 UTF-8 字节

docker compose exec mysql bash -c "grep \"sys.site.loginDesc\" /docker-entrypoint-initdb.d/01-init.sql | od -An -tx1 | head"
# 容器内同一段字节完全一致 → 挂载没坏

再查库里同一段:

1
2
3
docker compose exec mysql mysql -uroot -p'***' --default-character-set=utf8mb4 your_db \
-e "SELECT HEX(config_value) FROM SYS_SITE_CONFIG WHERE config_key='sys.site.loginDesc';"
# 3F3F3F... → 库里已是问号

=> 文件进容器是好的,但 mysql < db.sql 这一步把有效 UTF-8 写成了 ?

4. 连接字符集是否正常

1
2
docker compose exec mysql mysql -uroot -p'***' --default-character-set=utf8mb4 your_db \
-e "SHOW VARIABLES LIKE 'character_set%';"
1
2
3
4
5
6
+--------------------------+--------------------------------+
| character_set_client | utf8mb4 |
| character_set_connection | utf8mb4 |
| character_set_database | utf8mb4 |
| character_set_server | utf8mb4 |
+--------------------------+

服务端全是 utf8mb4,但最小化写入却正常:

1
docker compose exec mysql bash -c "mysql -uroot -p'***' --default-character-set=utf8mb4 your_db -e \"SET NAMES utf8mb4; INSERT INTO SYS_SITE_CONFIG (config_key, config_value) VALUES ('test.chinese','集打卡') ON DUPLICATE KEY UPDATE config_value='集打卡'; SELECT config_value, HEX(config_value) FROM SYS_SITE_CONFIG WHERE config_key='test.chinese';\""
1
2
3
4
5
+--------------+--------------------+
| config_value | HEX(config_value) |
+--------------+--------------------+
| 集打卡 | E99B86E68993E58DA1 |
+--------------+--------------------+

结论:服务端能存中文,问题只在 docker compose exec -T mysql ... < db.sql 这种宿主机管道

5. 根因

docker compose exec -T 的 stdin 在当前环境下会对中文做文本转码,导致有效 UTF-8 进到 mysql 客户端前就被替换为 ?。错误路径:

1
2
3
4
主机 db.sql (UTF-8 正确)
→ docker exec -T stdin 管道(转码)
→ mysql 客户端(收到已是 3F)
→ 存库 3F

而直接字符串 INSERT '集打卡' 不走管道,所以正常。

6. 修复

拷进容器再导,绕开管道:

1
2
3
4
5
docker compose exec mysql mysql -uroot -p'***' -e "DROP DATABASE your_db; CREATE DATABASE your_db CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;"
docker cp db.sql $(docker compose ps -q mysql):/tmp/db.sql
docker compose exec mysql bash -c "mysql -uroot -p'***' --default-character-set=utf8mb4 your_db < /tmp/db.sql"
docker compose exec mysql mysql -uroot -p'***' --default-character-set=utf8mb4 your_db \
-e "SELECT config_value FROM SYS_SITE_CONFIG WHERE config_key='sys.site.loginDesc';"
1
2
3
+-----------------------------------------------------------------------------------------------------------------+
| 集打卡、考勤、业绩统计于一体的现代化直播运营平台,简洁高效,助力团队协作。 |
+-----------------------------------------------------------------------------------------------------------------+

7. 自动化避免复发

docker-compose.yml 改为首次建库时容器内导入:

1
2
3
4
volumes:
- mysql-data:/var/lib/mysql
- ./db.sql:/tmp/db.sql:ro
- ./docker/mysql-init.sh:/docker-entrypoint-initdb.d/01-init.sh:ro

docker/mysql-init.sh

1
2
3
#!/bin/bash
set -e
mysql --default-character-set=utf8mb4 -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" < /tmp/db.sql

新部署 docker compose down -v && docker compose up -d 自动正确;已有库的重导仍用上面的 docker cp 方式,并始终加 --default-character-set=utf8mb4

8. 教训

  1. 3F 的 HEX 且 © 正常,是写入时转码的典型特征(中文映射不到目标字符集)。
  2. SHOW CREATE TABLEcharacter_set% 都正常时,重点查导入管道,而非表结构。
  3. db.sql 保持 UTF-8 无 BOM,用 scp/git 传输,别用会转码的在线编辑器。