MySQL 5.6+ 的 TIMESTAMP(3) 和 DATETIME(3) 是唯一可靠方案,需显式声明精度、匹配函数(如 NOW(3))、统一三位毫秒格式,并确保全链路时钟一致。

MySQL 5.6+ 的 TIMESTAMP(3) 和 DATETIME(3) 是唯一可靠方案
MySQL 在 5.6 版本起才支持微秒级(实际精度为 1μs,但常用毫秒即 (3))的时间类型。低于该版本的 TIMESTAMP 或 DATETIME 默认只到秒级,强行插入 '2024-01-01 12:00:00.123' 会被截断为 '2024-01-01 12:00:00',且无任何警告。
必须显式声明精度:使用 TIMESTAMP(3) 存储带时区的毫秒时间,或 DATETIME(3) 存储无时区的本地毫秒时间。二者在索引、比较、函数支持上行为一致,区别仅在于时区处理逻辑。
-
TIMESTAMP(3)插入时自动转为 UTC 存储,查询时按当前会话时区转换回本地时间 —— 若业务跨时区且需统一基准,选它 -
DATETIME(3)原样存储,不涉及时区转换 —— 多数日志、事件时间戳场景更直观,推荐优先用 - 建表时漏写括号里的数字(如写成
DATETIME而非DATETIME(3))等于白做,精度仍为秒级
INSERT 和 NOW() 必须匹配精度,否则毫秒丢失
即使字段定义为 DATETIME(3),若插入值没带毫秒部分,或用 NOW()(默认无精度)而非 NOW(3),结果仍是秒级。
常见错误写法:INSERT INTO events(ts) VALUES (NOW()); → 存进去是 '2024-01-01 12:00:00.000',不是你想要的实时毫秒。
- 正确写法:
INSERT INTO events(ts) VALUES (NOW(3));或显式字符串'2024-01-01 12:00:00.123' - 应用层生成时间(如 Python
datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:23])务必截到毫秒位(23 位长度),否则 MySQL 可能报错或截断 -
SYS_DATE()、CURRENT_TIMESTAMP()同样需加精度参数,如CURRENT_TIMESTAMP(3)
WHERE 查询中毫秒比较必须注意字符串格式和隐式转换
直接用 WHERE ts > '2024-01-01 12:00:00.123' 没问题,但若字符串少于三位毫秒(如 '2024-01-01 12:00:00.1'),MySQL 会补零(变成 .100),而 '2024-01-01 12:00:00.12' 补成 .120 —— 看似省事,实则易引发边界误判。
- 统一用三位毫秒格式(
.123),避免依赖自动补零逻辑 - 用函数比较更安全:例如
WHERE ts > DATE_SUB(NOW(3), INTERVAL 5000 MICROSECOND),避免字符串解析歧义 -
BETWEEN对毫秒边界敏感:ts BETWEEN '2024-01-01 12:00:00.000' AND '2024-01-01 12:00:00.999'是完整覆盖一秒,但AND '2024-01-01 12:00:00.99'实际等价于.990,漏掉最后 9 毫秒
索引和 ORDER BY 性能不受毫秒精度影响,但要注意排序稳定性
DATETIME(3) 字段加普通 B-tree 索引,查询性能与秒级时间无差异 —— 精度提升不带来额外开销。但排序时若存在大量相同秒级时间戳,毫秒部分决定了最终顺序,这点常被忽略。
- 如果业务要求“同一秒内事件严格按发生顺序排列”,必须确保写入时毫秒真实准确(比如用
NOW(3)而非应用层生成后传入) - 批量插入时若所有行都用同一个
NOW(3)值,毫秒部分完全一致,ORDER BY ts无法保证行间相对顺序 —— 需额外字段(如自增 ID)辅助排序 - 不要试图用
CAST(ts AS DECIMAL)提取毫秒做计算,既慢又易出错;需要毫秒差值请用TIMESTAMPDIFF(MICROSECOND, t1, t2)再除以 1000
真正麻烦的不是存或查,而是全链路时间源的一致性:MySQL 服务时钟、应用服务器时钟、客户端生成时间,三者哪怕差几毫秒,在高频写入下就会暴露排序混乱或去重失败。别只盯着 SQL 语法。


















