UNIX_TIMESTAMP函数仅返回秒级时间戳,不支持毫秒;需结合DATE_FORMAT截断和EXTRACT(MICROSECOND)提取后换算,再乘1000相加并FLOOR取整,才能安全生成整型毫秒时间戳。

UNIX_TIMESTAMP 函数本身不支持毫秒级输出
UNIX_TIMESTAMP() 返回的是秒级整数,无论传入什么精度的时间值,结果都只精确到秒。比如 UNIX_TIMESTAMP('2024-01-01 12:00:00.123') 和 UNIX_TIMESTAMP('2024-01-01 12:00:00.999') 都返回同一个值 1704110400。这不是 bug,是函数设计如此——它本质上是计算「自 Unix 纪元起的秒数」。
想得到毫秒级时间戳,必须手动补上毫秒部分:
- 用
EXTRACT(MICROSECOND FROM ...)提取微秒(注意:不是毫秒),再除以 1000 得到毫秒小数 - 把
UNIX_TIMESTAMP(...)的秒数乘以 1000,加上毫秒小数,最后FLOOR()或ROUND()得到整型毫秒戳 - 更稳妥的做法是用
DATE_FORMAT(..., '%Y-%m-%d %H:%i:%s')截掉毫秒再转秒,避免因微秒进位导致秒数跳变
正确构造毫秒级时间戳的 SQL 表达式
假设你有一个带毫秒的 DATETIME(3) 字段 created_at,要转成毫秒级时间戳(整数),推荐写法是:
SELECT UNIX_TIMESTAMP(DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s')) * 1000 + FLOOR(EXTRACT(MICROSECOND FROM created_at) / 1000) AS ms_timestamp FROM your_table;
这个表达式拆解为:
-
DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s'):安全截断毫秒,避免UNIX_TIMESTAMP()对.999这类值向上进位(MySQL 5.6+ 中,UNIX_TIMESTAMP('2024-01-01 00:00:00.999')实际会返回下一秒) -
EXTRACT(MICROSECOND FROM created_at):获取 0–999999 范围的微秒值,除以 1000 后取整即得毫秒(0–999) - 乘 1000 后相加,确保结果是严格单调递增的毫秒戳
INSERT 或 UPDATE 时生成毫秒时间戳要注意精度丢失
MySQL 的 NOW(3)、CURRENT_TIMESTAMP(3) 能存毫秒,但一旦经过 UNIX_TIMESTAMP() 就丢光了。所以如果业务依赖毫秒级唯一性或排序,别在 SQL 层“先转毫秒再存”,而应:
- 直接用
DATETIME(3)或TIMESTAMP(3)存原始时间,需要毫秒戳时再按上一节方法实时计算 - 如果必须存毫秒整数,建议在应用层生成(如 Python 的
int(time.time() * 1000)),避免 MySQL 内部浮点运算和时区转换引入误差 -
UNIX_TIMESTAMP(NOW(3))仍是秒级,不要误以为括号里的(3)会影响返回值精度
跨版本兼容性:MySQL 5.6 vs 8.0 的微秒行为差异
MySQL 5.6 不支持 EXTRACT(MICROSECOND FROM ...) 对 NULL 或非法时间的静默处理,出错会中断查询;MySQL 8.0 则更宽容。另外:
- 5.6 中
UNIX_TIMESTAMP('2024-01-01 00:00:00.500')可能四舍五入到1704110401(即进位),8.0 默认向下截断(文档称“truncates fractional seconds”) - 若需稳定行为,统一用
DATE_FORMAT(..., '%Y-%m-%d %H:%i:%s')预处理,不依赖版本对小数秒的隐式处理 -
FLOOR()比ROUND()更安全,避免.999毫秒被 round 成 1 导致秒数意外+1
真正麻烦的不是怎么算,而是不同场景下“毫秒”的语义是否一致——比如日志打点、分布式 ID、缓存过期,各自对精度、单调性、时区的要求都不一样,得按需选方案,不能一套表达式打天下。


















