必须同时对齐服务端、客户端、字段类型三层时区规则,否则时间仍会差8小时;mysql实际时区需查运行时值,docker/云环境常因system指向空软链或utc导致偏移;永久修改需在my.cnf中设default-time-zone='+08:00'并重启;jdbc连接须显式指定servertimezone=asia/shanghai;timestamp存utc、随会话时区动态转换,datetime为字面值、不受时区影响;线上建议统一用datetime并由应用层约定语义。

直接改 default-time-zone 不一定能修好,必须同时对齐服务端、客户端、字段类型三层时区规则,否则时间仍会差 8 小时或忽快忽慢。
查清 MySQL 实际生效的时区值
别信配置文件里写了什么,要查运行时真实值:
- 执行
SELECT @@global.time_zone, @@session.time_zone, @@system_time_zone;—— 如果@@global.time_zone返回SYSTEM,说明它正读宿主机时区;此时再查系统时区:timedatectl | grep "Time zone" - 在 Docker 或云环境里,
SYSTEM常指向一个空软链或 UTC,导致 MySQL 表面没报错,实际偏移 8 小时 - 验证偏移是否符合预期:执行
SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP);,返回+08:00才是东八区正确状态
永久统一服务端时区(必须重启)
临时 SET GLOBAL time_zone = '+08:00' 重启即丢,生产环境必须落盘:
- 编辑
/etc/my.cnf或/etc/mysql/mysql.conf.d/mysqld.cnf,在[mysqld]段下加:default-time-zone = '+08:00' - 不推荐用
Asia/Shanghai—— 它依赖系统tzdata和 MySQL 时区表,而官方 Docker 镜像默认不带tzdata,极易 fallback 到SYSTEM - 改完必须重启:
sudo systemctl restart mysql,重启后立刻验证SELECT @@global.time_zone;是否真为'+08:00'
JDBC 连接必须显式声明 serverTimezone
即使服务端设成 +08:00,JDBC 8.0+ 驱动默认仍按 JVM 本地时区解析 TIMESTAMP,造成双重转换:
- 连接串必须含:
?serverTimezone=Asia/Shanghai&useTimezone=true(优先用 IANA 名,GMT%2B8在部分驱动中不识别) -
spring.datasource.url中必须包含该参数;spring.jackson.time-zone对数据库层完全无效 - 验证方式:连接后执行
SELECT @@session.time_zone;,确保返回值与你期望一致;老连接不会自动更新,只影响新连接
区分 TIMESTAMP 和 DATETIME 的行为差异
这是“部分时间对得上、部分不对”的根本原因:
-
TIMESTAMP存的是 UTC 时间戳,写入时转 UTC,读取时按当前session time_zone转回字面值;改会话时区,同一行查出来就变 -
DATETIME是纯字面值,time_zone设置对它完全无影响;迁移后若历史数据是按东八区字符串插入的DATETIME,那它就永远是东八区字面值 - 线上系统建议统一用
DATETIME,并由应用层明确约定语义(比如“全部存 UTC”或“全部存 Asia/Shanghai”),避免隐式转换带来的歧义
最易被忽略的是:就算所有配置都对了,如果表里混用了 TIMESTAMP 和 DATETIME,且应用代码在插入前又做了本地时区格式化,那修复时区只是治标——真正要做的,是让字段定义和业务逻辑自洽,而不是靠参数强行拉齐。











