MySQL迁移后时间错乱主因是time_zone未同步,需统一配置+08:00、校验容器时区、JDBC serverTimezone匹配及区分TIMESTAMP/DATETIME时区行为。

MySQL迁移后时间错乱,几乎可以确定是 time_zone 配置没同步,而不是系统时间本身不准。新实例默认常为 SYSTEM,而旧库大概率设了 '+08:00' 或 'Asia/Shanghai';一旦容器或云主机的系统时区不可靠,SYSTEM 就会失效。
查清迁移前后实际生效的 time_zone 值
别只看 SELECT NOW(),得拆开看四层:
-
SELECT @@global.time_zone, @@session.time_zone, @@system_time_zone;—— 重点关注前两个是否一致、是否为你预期的值(如'+08:00') -
timedatectl status | grep "Time zone"(Linux)或date—— 确认宿主机/容器系统时区是否真实为东八区 - 对比旧库备份中的
my.cnf,找default-time-zone行;如果旧库是default-time-zone = '+08:00',新库必须一模一样 - 在 Docker/K8s 环境中,即使配置写了
default-time-zone = '+08:00',若容器内缺失/usr/share/zoneinfo/Asia/Shanghai,MySQL 启动时会静默 fallback 到SYSTEM,此时SELECT @@global.time_zone仍显示SYSTEM
永久设置 default-time-zone = '+08:00' 并重启 mysqld
这是最稳定、最可移植的做法,不依赖系统 tzdata:
- 编辑
/etc/my.cnf或/etc/mysql/my.cnf,在[mysqld]段下添加:default-time-zone = '+08:00' - 必须重启:执行
systemctl restart mysqld(或对应服务名),热重载不生效 - 验证:重启后执行
SELECT @@global.time_zone;,返回值必须是'+08:00',不是SYSTEM或空 - 避免用
Asia/Shanghai:它需要提前运行mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql,且后续系统 tzdata 升级可能引入夏令时偏差
JDBC 连接串必须显式指定 serverTimezone=Asia/Shanghai
MySQL 服务端设成 '+08:00' 不代表 Java 应用就安全了——JDBC 8.0+ 驱动默认不信任服务端声明,会按 JVM 本地时区二次转换:
- 正确写法:
?serverTimezone=Asia/Shanghai&useTimezone=true(注意 URL 编码) - 错误写法:
?serverTimezone=GMT%2B8(部分驱动不识别)、?serverTimezone=CST(歧义太大,Java 默认当 UTC-6 解析) - Spring Boot 用户:必须写在
spring.datasource.url里,spring.jackson.time-zone对数据库层完全无效 - 如果历史数据用
DATETIME存了“看起来像北京时间”的字符串(实为字面值),改时区不会修复它;只能靠DATE_ADD(created_at, INTERVAL 8 HOUR)批量修正
DATETIME 和 TIMESTAMP 的行为差异必须立刻厘清
这是导致“部分时间对得上、部分不对”的根源:
-
DATETIME是纯字面值:存什么查什么,time_zone设置对它完全无影响 -
TIMESTAMP存的是 UTC:写入时自动转 UTC,读取时再按当前session的time_zone转回——所以同一个字段,在 Navicat、命令行、Java 应用里查出来可能完全不同 - 线上系统强烈建议统一用
DATETIME,并由应用层明确约定语义(比如“全部存 UTC 字符串”或“全部存东八区格式字符串”) - 如果必须用
TIMESTAMP,务必确保所有连接(包括备份脚本、DBA 工具)都强制执行SET time_zone = '+08:00',不能依赖init_connect(它对SUPER权限用户无效)
真正难的不是配置,而是历史数据:如果旧库用 SYSTEM 且系统时区曾被手动改过,那些 TIMESTAMP 字段已经按错误规则存进去了,改 time_zone 只会让读取结果更错。这时候得用 CONVERT_TZ() 或批量更新,而不是寄希望于一个配置项。


















