深入解析 MySQL 中的时间函数:NOW() 与 SYSDATE() 的奥秘
在 MySQL 中,NOW()
和 SYSDATE()
是两个常用的时间函数,它们都能返回当前时间,但背后的行为逻辑却截然不同。本文将结合 MySQL 8 的源码实现,深入探讨它们的核心区别、适用场景,以及如何通过设计优化性能和保证数据一致性。
一、基础用法与直观区别
1. NOW()
:事务的“时间快照”
-- 事务内多次调用 NOW(),返回相同的时间
START TRANSACTION;
SELECT NOW(); -- 返回时间 T1
SELECT SLEEP(2);
SELECT NOW(); -- 仍返回 T1
COMMIT;
特点:
- 在事务或查询开始时记录时间,后续调用均返回该缓存值。
- 确保事务内时间一致性,适合需要原子性操作的场景。
2. SYSDATE()
:实时的“系统时钟”
-- 每次调用返回实时时间
SELECT SYSDATE(); -- 返回时间 T1
SELECT SLEEP(2);
SELECT SYSDATE(); -- 返回时间 T1+2
特点:
- 每次调用直接读取操作系统时间,结果随时间推移变化。
- 适合需要精确实时性的场景(如监控日志),但事务中可能导致数据逻辑问题。
二、核心区别与设计原理
1. 事务一致性 vs 实时性
-
NOW()
在查询开始时通过THD
(线程描述符)缓存时间,确保整个事务内所有操作基于同一时间基准。// 源码逻辑:从 THD 中获取缓存的查询开始时间 void THD::set_time() { start_utime = my_micro_time(); // 记录系统时间到线程上下文 }
-
SYSDATE()
每次调用均触发系统调用(如gettimeofday()
),获取实时时间。// 源码逻辑:直接读取系统时间 bool Item_func_sysdate_local::get_date(MYSQL_TIME *now_time) { const ulonglong tmp = my_micro_time(); // 实时获取系统时间 thd->time_zone()->gmt_sec_to_TIME(now_time, tmp / 1000000); }
2. 性能开销
-
NOW()
缓存机制减少系统调用,性能更高。
适用场景:高频查询、事务处理。 -
SYSDATE()
频繁系统调用增加 CPU 开销。
适用场景:低频但需要高精度实时性的操作。
3. 时间精度控制
两者均支持指定小数位数(如 NOW(3)
返回毫秒级时间),但实现方式不同:
NOW(n)
:从缓存的微秒时间戳截断到指定精度。// 源码逻辑:截断微秒部分 my_timeval THD::query_start_timeval_trunc(uint8 decimals) { my_timeval tv = {start_time.tv_sec, start_time.tv_usec}; timeval_trunc(&tv, decimals); // 如 decimals=3 → 保留毫秒 return tv; }
SYSDATE(n)
:直接获取实时时间并截断。
三、源码解析:时间如何被缓存和传递?
1. NOW()
的时间缓存机制
-
步骤 1:查询初始化
执行 SQL 前,调用THD::set_time()
记录时间到线程上下文。// sql/sql_class.cc void THD::set_time() { start_utime = my_micro_time(); // 微秒级时间戳 my_micro_time_to_timeval(start_utime, &start_time); // 转为 timeval 结构 }
-
步骤 2:调用
NOW()
从THD
中读取缓存的start_time
,生成格式化时间。// sql/item_timefunc.cc longlong Item_func_now::val_date_temporal() { MYSQL_TIME_cache tm; tm.set_datetime(thd->query_start_timeval_trunc(decimals), decimals, time_zone()); return tm.val_packed(); }
2. SYSDATE()
的实时获取
- 每次调用执行系统调用,无缓存:
// sql/item_timefunc.cc bool Item_func_sysdate_local::get_date(MYSQL_TIME *now_time) { const ulonglong tmp = my_micro_time(); // 实时获取时间 thd->time_zone()->gmt_sec_to_TIME(now_time, tmp / 1000000); }
四、如何选择:NOW()
还是 SYSDATE()
?
1. 事务场景
-
使用
NOW()
确保事务内时间一致,例如订单创建时间、批量数据处理。INSERT INTO orders (create_time, ...) VALUES (NOW(), ...); -- 同一事务内所有订单的 create_time 相同
-
避免
SYSDATE()
可能导致时间跳跃,破坏事务原子性。
2. 监控与审计
- 使用
SYSDATE()
记录操作发生的精确时间,如登录日志、安全审计。INSERT INTO access_log (event_time, ...) VALUES (SYSDATE(), ...); -- 记录实时操作时间
3. 高并发场景
- 优先
NOW()
减少系统调用,提升性能。
五、总结
特性 | NOW() | SYSDATE() |
---|---|---|
事务一致性 | ✅ 同一事务内时间不变 | ❌ 每次调用实时更新 |
性能 | ✅ 高效(缓存机制) | ❌ 较高开销(频繁系统调用) |
适用场景 | 事务处理、高频查询 | 实时监控、审计日志 |
精度控制 | 支持(如 NOW(3) ) | 支持(如 SYSDATE(3) ) |
理解 NOW()
和 SYSDATE()
的区别,能够帮助开发者根据场景选择更合适的函数,从而优化系统性能并避免潜在逻辑错误。MySQL 通过缓存机制和实时获取的平衡设计,为不同需求提供了灵活的时间处理方案。
最后的小测试:
尝试在事务中混用 NOW()
和 SYSDATE()
,观察结果差异。例如:
START TRANSACTION;
SELECT NOW(), SYSDATE(), SLEEP(2);
SELECT NOW(), SYSDATE();
COMMIT;
你会发现:NOW()
保持不变,而 SYSDATE()
随时间推移递增!这就是 MySQL 时间处理的精妙之处。