MySQL 时区设置指南|Docker Compose 部署踩坑实录
在使用 Docker Compose 部署 MySQL 时,经常会遇到时区不一致的问题:查询 NOW() 返回的时间与本地时间相差 8 小时,或者 system_time_zone 显示为 UTC 但业务需要东八区时间。本文将从查看当前时区开始,逐步分析配置失效的原因,并给出三种稳定可靠的解决方案。
查看 MySQL 当前时区
MySQL 提供了三个关键变量用于时区控制:
SELECT @@global.time_zone, @@session.time_zone, @@system_time_zone;@@global.time_zone:全局默认时区,影响所有新建立的会话@@session.time_zone:当前会话时区,可通过SET time_zone = ...临时修改@@system_time_zone:MySQL 启动时从操作系统读取的时区,只读
另外也可以使用 SHOW VARIABLES LIKE 'time_zone'; 查看当前会话时区。如果返回 SYSTEM,则表示实际时区跟随 system_time_zone。
Docker Compose 部署中的典型配置方式
通常我们会在 docker-compose.yml 中通过环境变量和启动参数来设定时区,常见做法有以下几种:
1. 设置容器系统时区(TZ 环境变量)
environment:
TZ: Asia/Shanghai此方式理论上会修改容器内操作系统的时区,从而影响 system_time_zone。但在某些 MySQL 镜像版本或自定义启动命令下,该变量可能被忽略。
2. 通过 MySQL 启动参数指定默认时区
command:
- --default-time-zone=+08:00直接在容器启动时传递给 mysqld,应直接改变 global.time_zone。
3. 挂载宿主机时区文件
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro此方法在 Linux 宿主机上有效,但 macOS 和 Windows 下不适用。
配置未生效的典型现象
一位开发者按照上述组合方式部署后,查询结果依然显示:
+--------------------+---------------------+--------------------+
| @@global.time_zone | @@session.time_zone | @@system_time_zone |
+--------------------+---------------------+--------------------+
| SYSTEM | SYSTEM | UTC |
+--------------------+---------------------+--------------------+尽管在 docker-compose.yml 中同时设置了 TZ: Asia/Shanghai 和 --default-time-zone=+08:00,但 MySQL 仍然使用了 UTC。
原因分析
TZ环境变量未能正确生效,可能由于镜像的 entrypoint 脚本未按预期处理,或者被自定义command覆盖了部分初始化逻辑。- 命令行传递的
--default-time-zone参数在某些 MySQL 8.0 版本中可能被my.cnf中的默认值覆盖,或者由于格式问题被忽略(例如缺少引号或与其他参数顺序冲突)。 - 更稳定的做法是使用配置文件,因为 MySQL 启动时会按顺序读取多个配置,
/etc/mysql/conf.d/下的.cnf文件优先级较高,可以确保设置被采纳。
推荐解决方案
方案一:通过挂载自定义配置文件 my.cnf(最稳定)
在宿主机 ./mysql/conf/ 目录下创建 timezone.cnf 文件,内容如下:
[mysqld]
default-time-zone = '+08:00'然后在 docker-compose.yml 中挂载该目录,并移除命令行中的 --default-time-zone 参数,避免冲突:
services:
mysql:
image: mysql:8.0
container_name: mysql
networks:
- shared_net
restart: always
environment:
TZ: Asia/Shanghai # 保留作为辅助
ports:
- "3306:3306"
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/conf:/etc/mysql/conf.d # 自定义配置目录
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --innodb-buffer-pool-size=256M
- --innodb-log-buffer-size=16M
- --performance-schema=OFF
- --tmp-table-size=32M
- --max-heap-table-size=32M
# --default-time-zone 已移至配置文件
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M配置文件方式不受容器系统时区影响,直接作用于 MySQL 服务层,是最可靠的途径。
方案二:同时修改容器系统时区(可选)
若希望 system_time_zone 也显示为 Asia/Shanghai,可以挂载宿主机的时区文件(仅限 Linux 宿主机):
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro这样容器内执行 date 命令也会输出东八区时间,与 MySQL 内部时区保持一致。
方案三:命令行加引号尝试(不推荐)
如果坚持用命令行,可尝试将参数改为 --default-time-zone='+08:00',但经验表明此方法依然可能被忽略,不推荐作为生产环境的配置。
验证生效
重启容器后,进入 MySQL 执行验证:
docker-compose down
docker-compose up -d
docker exec -it mysql mysql -p -e "SELECT @@global.time_zone, @@session.time_zone, @@system_time_zone;"预期输出:
+--------------------+---------------------+--------------------+
| @@global.time_zone | @@session.time_zone | @@system_time_zone |
+--------------------+---------------------+--------------------+
| +08:00 | +08:00 | Asia/Shanghai |
+--------------------+---------------------+--------------------+如果挂载了宿主机时区文件,system_time_zone 也会变为 Asia/Shanghai;若仅通过配置文件设置,则 system_time_zone 可能仍为 UTC,但 global.time_zone 为 +08:00,这已经满足大多数业务需求(NOW() 等函数将返回 +8 区时间)。
注意事项
- 修改时区不会影响已存储的
DATETIME和TIMESTAMP类型数据的值,但会影响NOW()、CURDATE()等时间函数的返回值。 - 如果宿主机是 macOS 或 Windows,挂载
/etc/localtime无效,此时只能依靠配置文件来保证 MySQL 内部时区正确。 - 在编写配置文件时,请确保文件扩展名为
.cnf,且放在/etc/mysql/conf.d/下,MySQL 会自动加载。 - 若同时存在多个配置文件,注意优先级顺序(通常按字母顺序加载,但最终参数以最后加载的为准),建议只放一个时区配置文件避免混淆。
总结
通过查看时区变量定位问题,结合 Docker Compose 的部署特点,我们认识到命令行参数和环境变量在某些场景下并不完全可靠。最稳健的做法是将 default-time-zone 写入独立的 .cnf 配置文件并挂载进容器。若需要统一容器系统时间,可额外挂载宿主机时区文件(Linux)。经过以上配置,即可确保 MySQL 在任何环境下都稳定输出东八区时间。
这一经验不仅适用于 MySQL,也适用于其他对时区敏感的中间件(如 Redis、Elasticsearch)的容器化部署:优先使用配置文件而非环境变量,往往能获得更可控的结果。
