$kernelink route --hydrate --safe

页面加载 /
跳到正文
auth://account/session

登录工作区

使用 Emlog 账户继续访问你的内容与互动记录。

忘记密码?

打开 Emlog 原生登录页

12.md
workspace / posts
~/posts/12.md 阅读中

Docker 部署 MySQL 8.0:初始化参数、字符集和几个必踩的坑

MySQL 容器化的坑集中在"初始化只做一次"这件事上:密码、库、字符集、大小写敏感,全是在数据目录为空的那一次初始化里定下来的,后面改环境变量、改配置文件都不会回头补。搞清这一点,剩下的就是挂卷、权限和备份。

一、镜像和目录

docker pull mysql:8.0
sudo mkdir -p /data/mysql/{data,conf,init}
宿主机目录 容器内路径 用途
/data/mysql/data /var/lib/mysql 数据文件
/data/mysql/conf /etc/mysql/conf.d 自定义配置
/data/mysql/init /docker-entrypoint-initdb.d 首次初始化执行的 SQL

/data/mysql/data 要给容器里的 mysql 用户(uid 999)权限:

sudo chown -R 999:999 /data/mysql/data

如果数据目录挂的是新格式化的独立分区,注意分区根目录里会有 lost+found。MySQL 初始化时要求目录为空,看到 lost+found 就认为"目录里有文件",直接放弃初始化。这种情况用分区下的子目录,比如 /data/mysql/data,而不是直接把分区挂到 /var/lib/mysql。

二、my.cnf

/data/mysql/conf/my.cnf:

[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_0900_ai_ci
lower_case_table_names = 1
default-time-zone = '+08:00'

max_connections = 500
innodb_buffer_pool_size = 1G

slow_query_log = 1
long_query_time = 1

几个参数说明:

参数 说明
character-set-server 默认字符集,8.0 默认就是 utf8mb4,写出来是为了明确
collation-server 8.0 的默认排序规则,从 5.7 迁过来的库要留意差异
lower_case_table_names 表名大小写是否敏感,只在初始化时生效
default-time-zone 容器时区是 UTC,业务按东八区存时间必须设
innodb_buffer_pool_size 通常给物理内存的 50% 到 70%,容器内存限制下别超过上限

配置文件权限有要求:MySQL 会忽略全局可写的配置文件。挂进去之后再确认一次:

sudo chmod 644 /data/mysql/conf/my.cnf

日志里出现 World-writable config file '/etc/mysql/conf.d/my.cnf' is ignored,就是权限太松,配置压根没被读。

三、compose

services:
  mysql:
    image: mysql:8.0
    container_name: mysql
    restart: always
    ports:
      - "3306:3306"
    environment:
      TZ: Asia/Shanghai
      MYSQL_ROOT_PASSWORD: 换成强密码
      MYSQL_DATABASE: app
      MYSQL_USER: app
      MYSQL_PASSWORD: 换成强密码
    volumes:
      - /data/mysql/data:/var/lib/mysql
      - /data/mysql/conf:/etc/mysql/conf.d:ro
      - /data/mysql/init:/docker-entrypoint-initdb.d:ro
    healthcheck:
      test: ["CMD-SHELL", "mysql -uroot -p\"$$MYSQL_ROOT_PASSWORD\" -e 'SELECT 1' >/dev/null 2>&1"]
      interval: 30s
      timeout: 5s
      retries: 5
      start_period: 60s

四个环境变量的实际行为:

变量 生效条件
MYSQL_ROOT_PASSWORD 只能二选一:不设它就得设 MYSQL_RANDOM_ROOT_PASSWORD,否则容器拒绝启动
MYSQL_DATABASE 仅在数据目录为空时建库
MYSQL_USER / MYSQL_PASSWORD 仅在初始化时建用户并授权到 MYSQL_DATABASE
四个变量整体 数据目录非空时全部忽略,改了不起任何作用

healthcheck 这里用的是 mysql -e 'SELECT 1',不是 mysqladmin ping。mysqladmin ping 只要服务端能响应就返回成功,密码错了也照样"通",检测不出认证问题。

start_period 给 60 秒是有必要的,首次初始化要建系统表,磁盘慢的机器上跑一两分钟很正常,这段时间里的失败不该算进 retries。

四、初始化脚本

/data/mysql/init/ 下的 .sql、.sh 文件只在数据目录为空时按字母顺序执行一次。常见的用法是建库、建只读账号:

CREATE DATABASE IF NOT EXISTS app DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
CREATE USER IF NOT EXISTS 'readonly'@'%' IDENTIFIED BY '只读密码';
GRANT SELECT ON app.* TO 'readonly'@'%';
FLUSH PRIVILEGES;

放进去之后想让它再跑一遍,只能删数据目录重建,没有别的办法。所以初始化脚本要么一次写全,要么后面手工执行 SQL,不要指望"补一个文件进去重启就好"。

五、验证

docker ps --filter name=mysql
docker exec mysql mysql -uroot -p'密码' -e "SELECT VERSION(), @@character_set_server, @@lower_case_table_names, @@time_zone;"
docker exec mysql mysql -uroot -p'密码' -e "SHOW DATABASES;"

四个值对上了,说明配置文件和初始化参数都生效:

8.0.x   utf8mb4   1   +08:00

再从外部连一次,确认端口映射和账号授权:

mysql -h 127.0.0.1 -P 3306 -uapp -p'密码' app

六、备份和恢复

逻辑备份,适合中小库和跨版本迁移:

docker exec mysql sh -c 'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" \
  --single-transaction --routines --triggers --events --set-gtid-purged=OFF app' \
  > /backup/app-$(date +%F).sql

--single-transaction 让 InnoDB 在一致性快照里导出,不锁表;--set-gtid-purged=OFF 避免恢复时报 GTID 冲突。

恢复:

docker exec -i mysql sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" app' < /backup/app-2026-10-29.sql

物理备份可以直接复制 /data/mysql/data,但必须在停库之后:

docker compose stop mysql
sudo tar -czf /backup/mysql-data-$(date +%F).tar.gz -C /data/mysql data
docker compose start mysql

热复制数据目录得到的文件不可用,InnoDB 的 redo 和双写缓冲在运行中不一致,恢复时会报错。

七、常见报错

现象 原因 处理
Access denied for user 'root'@'localhost' 数据目录已存在,初始化变量被忽略,密码还是旧的 用旧密码进,或 --skip-grant-tables 重置
[ERROR] --initialize specified but the data directory has files in it 挂载目录非空,常见是 lost+found 换个空子目录
容器反复重启,日志 Different lower_case_table_names settings for server ('1') and data dictionary ('0') 初始化后改了 lower_case_table_names 这个值初始化后不能改,只能重建库
Authentication plugin 'caching_sha2_password' cannot be loaded 老客户端不认 8.0 的默认认证插件 升级驱动,或 ALTER USER ... IDENTIFIED WITH mysql_native_password BY ...
World-writable config file ... is ignored 配置文件权限太松 chmod 644
mysqld: Can't read dir of '/etc/mysql/conf.d/' (Errcode: 13) 目录权限或 SELinux 修权限,CentOS 上挂载加 :z
容器被 OOMKilled innodb_buffer_pool_size 超过容器内存上限 调小 buffer pool,或提高内存限制
启动日志 Permission denied 指向 /var/lib/mysql 数据目录属主不是 999 chown -R 999:999
Can't connect to local MySQL server through socket 服务没起来,还是初始化中 docker logs mysql 看最后 50 行

八、8.4 和迁移

如果准备用 8.4,先确认客户端兼容性:mysql_native_password 在 8.4 里默认不加载,老版本驱动和工具连上来会失败。新项目直接用 8.4 没问题,存量系统升级前要在测试环境把驱动、中间件、备份脚本都过一遍。

跨大版本迁移(5.7 → 8.0)不要靠复制数据目录,走 mysqldump 导出再导入,或者用官方的 mysqlsh 升级检查工具,先把不兼容项列出来。

最后一条限制:容器里的 MySQL 性能和裸机没有本质区别,瓶颈仍然是磁盘 IO 和内存。把数据目录放在网络存储(NFS、CIFS)上跑 MySQL 属于高风险操作,遇到 fsync 语义不一致会直接损坏数据,这一点和容器无关,但容器化之后更容易被顺手挂上去。

🤞 分享