MySQL事务时间戳异常90%以上源于系统时钟跳变,需禁用chronyd的step校正、仅启用slewing,并确保offset稳定在±10ms内,否则必须停写并重启mysqld;binlog时间戳跳跃即已污染,应切换新binlog并验证时间连续性。

MySQL事务时间戳异常,90%以上是系统时钟跳变(如手动 date -s、NTP未稳态下强制校正、虚拟机休眠唤醒)直接导致的。它不报错,但 NOW()、CURRENT_TIMESTAMP、binlog event time、甚至 InnoDB 行版本时间戳会突变或回退——这不是 MySQL 能修复的问题,必须从系统层掐断源头。
为什么 chronyd 或 systemd-timesyncd 不能完全避免跳变
NTP 客户端默认策略是“步进”(step)还是“ slewing ”(平滑调整),决定了是否引发跳变:
-
chronyd默认启用 slewing,但若时钟偏移 > 0.128 秒(默认阈值),它会强制 step —— 这就是你看到NOW()突然快/慢几分钟的根源 -
systemd-timesyncd没有 slewing 能力,只要检测到偏移 > 1 秒,就直接 step,毫无缓冲 - 云环境或虚拟机中,
vmware-tools或qemu-ga可能干扰 NTP,造成反复 step
验证当前行为:chronyc tracking | grep "Leap status" 若显示 Leap second happened 或 Step,说明刚发生过跳变;timedatectl timesync-status 中的 offset 若长期在 ±50ms 外震荡,也暗示同步不稳定。
如何禁用 step,只允许 slewing
对 chronyd(推荐用于生产库),编辑 /etc/chrony.conf,添加或修改两行:
makestep 0.1 -1 rtcsync
其中 makestep 0.1 -1 表示:偏移 ≤ 100ms 时只 slewing;>100ms 时也不 step(-1 表示禁用 step);rtcsync 确保硬件时钟持续同步,减少重启后大偏差。
改完后执行:sudo systemctl restart chronyd && chronyc makestep(触发一次强制 slewing 校准)。
注意:systemd-timesyncd 不支持禁用 step,线上 MySQL 节点请换用 chronyd。
MySQL 启动后系统时间被改了怎么办
MySQL 不监听系统时钟变化,NOW() 值一旦因跳变失准,只能重启 mysqld 才能重新读取系统时间并刷新时区缓存。但重启不是万能解法:
- 主从复制中,若仅重启从库,
binlog_format=ROW下事务时间戳仍按原错值写入 relay log,可能引发 GTID 或事件顺序错乱 - 使用
TIMESTAMP字段的表,跳变期间插入的数据会以错误 UTC 时间存储,无法回溯修正 - 容器化部署中,宿主机时间跳变会直接影响所有容器内 MySQL 实例
所以真正有效的动作只有两个:立即停止写入(如 SET GLOBAL read_only = ON),然后确认 chronyc tracking 显示 offset 稳定在 ±10ms 内,再重启 mysqld。
如何验证 binlog 时间戳是否已受跳变污染
不要只查 SELECT NOW(),要直接看 binlog 事件时间戳是否连续:
执行:mysqlbinlog --base64-output=DECODE-ROWS -v /var/lib/mysql/binlog.000001 | grep "^# at\|^INSERT\|^UPDATE" | head -20
观察每条事件前的 # timestamp 行(例如 #260507 14:22:03 server id 1 end_log_pos 1234)。如果出现明显跳跃(如前一条是 14:22:03,下一条变成 14:18:55),说明该 binlog 已被跳变污染,不可用于恢复或同步。
此时应:切换新 binlog 文件(FLUSH BINARY LOGS),并检查新文件首条事件时间是否与 NOW() 一致;若不一致,说明系统时间仍未稳,继续等。
最常被忽略的一点:即使 NTP 服务显示 active,只要 chronyc tracking 的 Last offset 绝对值大于 50ms,或者 Skew 长期 > 100ppm,就说明它正在“追赶”,此时任何依赖时间戳的事务都处于风险中——别等出问题才查。


















