根本原因是mysql默认使用系统时区(如utc),而应用期望asia/shanghai;需在my.cnf中设default-time-zone='+08:00'并重启,同时确保客户端连接串或会话不覆盖该配置,且时区表已加载。

MySQL 启动时没设对 default-time-zone,连接后 NOW() 和系统时间差 8 小时
根本原因不是 MySQL “错了”,而是它默认用系统时区(比如服务器在 UTC),而你的应用期望是 Asia/Shanghai。不改配置,光靠 SQL 临时设时区(如 SET time_zone = '+08:00')只影响当前会话,新连接又回 UTC。
实操建议:
- 确认系统时区:
timedatectl status或date -R,别只看date输出——有些系统date显示本地时间但 MySQL 仍读取 UTC - 修改 MySQL 配置文件(通常是
/etc/my.cnf或/etc/mysql/my.cnf),在[mysqld]段下加一行:default-time-zone = '+08:00'(推荐用偏移量,比Asia/Shanghai更稳定,避免夏令时或 tzdata 版本差异) - 重启 MySQL:
sudo systemctl restart mysql(或mariadb),不重启配置不生效 - 验证是否生效:
SELECT @@global.time_zone, @@session.time_zone;,两个都应返回+08:00
应用连接时被客户端时区覆盖,CONVERT_TZ() 结果异常
即使 MySQL 全局设了 +08:00,如果 JDBC、PHP PDO 或 Python MySQLdb 在连接串里显式传了 serverTimezone=UTC,或者客户端本身设置了 time_zone 会话变量,就会绕过全局配置。
实操建议:
- JDBC 连接串务必加上:
&serverTimezone=GMT%2B8(注意 URL 编码),不要用Asia/Shanghai,某些旧版驱动不识别 - PHP PDO 建议在
new PDO()后立刻执行:$pdo->exec("SET time_zone = '+08:00'");,比依赖 DSN 更可靠 - 检查有没有应用代码在连接后执行了
SET time_zone = 'SYSTEM'或SET time_zone = 'UTC',这种语句会直接覆盖全局 -
CONVERT_TZ('2024-01-01 12:00:00', '+00:00', '+08:00')要求源/目标都是有效时区字符串或偏移量;若传入'UTC'但 MySQL 未加载时区表,会返回NULL
用了 timestamp 类型却存成 UTC,查出来却像“少了 8 小时”
timestamp 类型本质是“带时区的存储”:写入时自动转成 UTC 存,读取时再按当前会话时区转回来。所以如果你会话时区是 +00:00,而数据本意是北京时间,就会显示错。
实操建议:
- 确认字段类型:用
DESC table_name;查列类型,datetime是纯字面值,不自动转换;timestamp才参与时区转换 - 统一会话时区:连接后立即执行
SET time_zone = '+08:00';,尤其在 ORM 初始化阶段做一次 - 如果业务逻辑强依赖本地时间且不想处理转换,直接把字段改成
datetime,并确保所有写入值已按本地时间构造(比如 PHP 用date('Y-m-d H:i:s')) - 别用
timestamp存毫秒级时间戳整数——它只支持到秒,且范围限于 1970–2038
MySQL 时区表没加载,CONVERT_TZ() 返回 NULL
MySQL 默认不加载时区表(mysql.time_zone_*),导致所有带时区名的函数(如 CONVERT_TZ('2024-01-01', 'UTC', 'Asia/Shanghai'))全部失效,静默返回 NULL。
实操建议:
- 先查表是否存在:
SELECT COUNT(*) FROM mysql.time_zone_name;,返回 0 就说明没加载 - 用系统命令加载(需有 FILE 权限):
mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql;路径可能为/usr/share/zoneinfo/posix或/usr/lib/zoneinfo,用find /usr -name "zoneinfo" 2>/dev/null确认 - 加载后重启 MySQL 或执行
FLUSH TIME ZONES;(5.7+ 支持) - 加载后仍要慎用时区名——不同系统
zoneinfo版本可能缺失Asia/Shanghai别名,优先用+08:00
最常被忽略的一点:Docker 容器里跑 MySQL,宿主机时区不会自动透传,/etc/localtime 和 /usr/share/zoneinfo 往往是空的或 UTC 的,必须显式挂载或在镜像里安装 tzdata。光改 my.cnf 没用。











