mysql时区必须在启动时通过配置文件设置default-time-zone='+08:00'并重启服务才真正生效,仅会话级set time_zone无效;timestamp自动时区转换,datetime不转换,混用易致8小时偏差。

MySQL 启动时设置 default-time-zone 才真正生效
MySQL 的时区不是靠连接时执行 SET time_zone = '+08:00' 就能一劳永逸的。那个只影响当前会话,且对 DEFAULT CURRENT_TIMESTAMP 这类列定义无效。真正起作用的是服务启动时加载的全局配置。
必须在 MySQL 配置文件(通常是 /etc/my.cnf 或 /etc/mysql/mysql.conf.d/mysqld.cnf)的 [mysqld] 段落下加:
[mysqld] default-time-zone = '+08:00'
然后重启 MySQL 服务(systemctl restart mysql 或 service mysqld restart)。不重启,配置不会加载。
- 不能写成
Asia/Shanghai—— MySQL 对命名时区支持有限,依赖系统tzdata且需运行mysql_tzinfo_to_sql导入,极易失败;直接用偏移量最稳妥 - 如果配置后仍显示
SYSTEM,说明配置未被读取:检查配置文件路径是否正确、是否在[mysqld]下、有无拼写错误(比如写成default-timezone少了连字符) - 修改后务必验证:
SELECT @@global.time_zone, @@session.time_zone;,两个都应返回+08:00
timestamp 列自动转换时区,datetime 不会
这是最容易引发时间偏差的隐形陷阱。MySQL 中 timestamp 类型值在存储和查询时会按 time_zone 设置自动做 UTC ↔ 本地时区转换;而 datetime 是“原样存、原样取”,完全不碰时区。
例如服务器设为 +08:00,插入 NOW():
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP→ 存的是 UTC 时间,查出来自动转成 +08:00 显示 -
created_at DATETIME DEFAULT NOW()→ 存的就是当前会话时区下的字面值,不转换
混用两种类型,或应用层又自行做一次时区转换,就会叠加出 8 小时偏差。建议统一用 timestamp 并确保全局时区固定,避免业务逻辑里再手动加减小时。
连接层时区不等于服务器时区,init_connect 不可靠
有些方案建议用 init_connect='SET time_zone = "+08:00"' 让每个连接自动设时区。这看似方便,但有硬伤:
- 该变量只对普通用户生效,root 和有
SUPER权限的用户会被跳过 - 如果客户端显式执行
SET time_zone,会覆盖它 - 对 JDBC、ORM(如 SQLAlchemy、MyBatis)等中间件,可能因连接池复用或驱动行为导致实际未执行
更稳的做法是:在应用代码中,连接建立后立刻执行一次 SET time_zone = '+08:00',并确认执行成功;同时仍保留 default-time-zone 配置兜底。
验证时区是否真正影响数据写入
光看 SELECT NOW() 返回北京时间,不代表数据安全。必须实测写入 + 查询全流程:
建表:
CREATE TABLE tz_test ( id INT PRIMARY KEY AUTO_INCREMENT, ts_col TIMESTAMP DEFAULT CURRENT_TIMESTAMP, dt_col DATETIME DEFAULT NOW() );
插入一行后立即查:
INSERT INTO tz_test () VALUES (); SELECT * FROM tz_test;
观察两列值是否一致(都应是当前北京时间)。如果不一致,说明 timestamp 被当成 UTC 存储了,意味着 default-time-zone 没生效,或者服务器系统时区本身是 UTC 且未覆盖。
这个测试比任何配置检查都直接——时间偏差问题最终一定暴露在数据上,而不是变量值里。











