MySQL 日期函数 NOW 和 SYSDATE 的区别
MySQL 日期函数 NOW 与 SYSDATE:核心区别与实战解析
在 MySQL 中获取当前日期和时间,你最常接触的两个函数莫过于 NOW() 和 SYSDATE()。表面上看,它们都能返回当前的日期时间值,但深入理解它们的执行机理差异,对于编写准确、可靠的 SQL 语句至关重要,尤其是在涉及事务、复制和长时运行语句时。本教程将带你从现象到本质,彻底搞清楚这对“双胞胎”函数的区别。
一、表象:看似相同的返回值
我们先通过一组简单的查询来观察二者的基本行为。
SELECT NOW(), SYSDATE();
在绝大多数情况下,单次执行的输出结果几乎相同,都类似于 2025-03-22 14:35:10。
SELECT NOW(), SLEEP(2), NOW(); -- 两个 NOW() 返回相同时间
SELECT SYSDATE(), SLEEP(2), SYSDATE(); -- 两个 SYSDATE() 可能返回不同时间
这里的差异开始显现:在同一个 SQL 语句中,多次调用 NOW() 返回的是语句开始执行时的时间常量;而多次调用 SYSDATE() 返回的是函数实际被调用时的实时时间。 这是理解两者区别的第一个关键点。
二、核心机制:执行时间与调用时间的对决
两种函数的本质区别在于它们对“当前时间”的获取时机不同。
2.1 NOW():语句级别的稳定快照
NOW() 返回的是 该语句开始执行的时刻 的时间。在语句执行期间,无论调用多少次,它都返回同一个不变的时间点。你可以将其视为一个在语句初始化时就被确定好的常量。
这一特性让 NOW() 在以下场景极具优势:
- 数据一致性:在
INSERT多行数据或批量更新时,同一个操作的所有行拥有完全一致的时间戳,便于后续审计和比对。 - 复制安全:在主从复制中,基于语句的复制(Statement-Based Replication)能够安全地重现
NOW()的值,因为它是一个确定的值。 - 索引友好:在查询条件中使用
NOW(),其稳定性允许优化器更好地利用索引。
2.2 SYSDATE():函数级别的实时跟踪
SYSDATE() 返回的是 函数实际被执行时 的操作系统时间。如果一条语句耗时较长,或者内部调用了 SLEEP() 等阻塞函数,后续的 SYSDATE() 调用会反映出真实的时间流逝。
这一特性使其适用于:
- 精确计时:在存储过程或长脚本中测量代码片段的实际耗时。
- 记录真实执行瞬间:但需要注意,这也意味着它在同一条语句内可能是不确定的,这为复制和查询缓存带来了隐患。
三、深入理解决定性的系统变量:sysdate-is-now
MySQL 通过一个名为 sysdate-is-now 的特殊系统变量,提供了强制让 SYSDATE() 表现得像 NOW() 的开关。该变量在官方文档中被标记为“弃用”,但依然广泛存在。
- 默认状态 (
--sysdate-is-now=OFF或不设置):SYSDATE()表现出实时性,与NOW()不同。 - 设置为 ON (
--sysdate-is-now=ON):SYSDATE()变为NOW()的别名,同样返回语句执行时的时间,失去实时性。
你可以通过以下方式查看当前会话的设置:
SHOW VARIABLES LIKE 'sysdate-is-now';
最佳实践建议: 不要修改该变量。因为它可能在未来版本被移除,且混用两种行为的语义会导致代码逻辑混乱。明确你的需求:如果需要语句级快照,就用 NOW();如果确实需要实时调用时间,就用 SYSDATE()。
四、实战案例:不同场景下的行为对比
下面通过几个典型场景,直观展示差异可能带来的影响。
4.1 批量插入时的差异
假设有一个日志表,我们需要为每一行插入的时间戳打标记。
CREATE TABLE event_log (
id INT AUTO_INCREMENT PRIMARY KEY,
event_name VARCHAR(50),
log_time_now DATETIME,
log_time_sysdate DATETIME
);
-- 插入三条记录,中间故意延迟
INSERT INTO event_log (event_name, log_time_now, log_time_sysdate)
VALUES
('Event A', NOW(), SYSDATE()),
('Event B', NOW(), SLEEP(1), SYSDATE()),
('Event C', NOW(), SYSDATE());
查询结果:
SELECT event_name, log_time_now, log_time_sysdate FROM event_log;
你会观察到:
log_time_now的三条记录完全一致,均为整个INSERT语句开始执行时的时间。log_time_sysdate的记录中,'Event B' 的值比 'Event A' 和 'Event C' 晚大约1秒,因为它调用了SLEEP(1),让SYSDATE()的调用时机推迟了。
4.2 触发器中的行为
在触发器里,NOW() 和 SYSDATE() 的差异同样明显。触发器作为触发语句的一部分,其执行时间归属于触发语句本身。
CREATE TABLE trigger_test (
id INT AUTO_INCREMENT PRIMARY KEY,
insert_time_now DATETIME,
trigger_time_now DATETIME,
trigger_time_sysdate DATETIME
);
DELIMITER //
CREATE TRIGGER before_insert_test
BEFORE INSERT ON trigger_test
FOR EACH ROW
BEGIN
SET NEW.trigger_time_now = NOW();
SET NEW.trigger_time_sysdate = SYSDATE();
END //
DELIMITER ;
-- 执行插入时会自行设定 insert_time_now 列
INSERT INTO trigger_test (insert_time_now) VALUES (NOW());
执行后,insert_time_now 和 trigger_time_now 永远相等,因为它们都基于同一语句开始时间。trigger_time_sysdate 则记录了触发器函数体被执行时那一刻的精确时间,可能完全相同,但在高并发或复杂触发条件下可能出现微秒级差异。
五、性能与复制影响:不可忽视的副产品
5.1 性能考量
NOW()被视为确定性函数,在查询优化阶段可以参与常数折叠、缓存等优化。在只读视图中使用尤为安全。SYSDATE()在默认设置下是非确定性的,这会导致:- 无法使用查询缓存:任何包含
SYSDATE()的语句结果都不会被缓存。 - 阻碍索引使用:在
WHERE条件中使用SYSDATE()时,优化器不会将其视为常量,可能放弃索引扫描。
- 无法使用查询缓存:任何包含
5.2 复制影响
对于基于语句的复制(SBR):
NOW()是安全的,主库上执行时的值会被记录并传递到从库。SYSDATE()由于不确定性,可能造成主从数据不一致。MySQL 在将SYSDATE()写入 binlog 时会自动将其替换为NOW()(除非启用了log_bin_use_v1_row_events等行级复制特性),但这实际上改变了原始语句的语义。因此,在复制环境中,应尽量避免在关键 SQL 中依赖SYSDATE()的实时行为,首选基于行的复制(RBR)或直接使用NOW()。
六、选择指南:何时使用哪个函数?
| 场景需求 | 推荐函数 | 理由 |
|---|---|---|
| 为数据行添加创建/更新时间戳,要求整条语句一致 | NOW() |
保证同一事务或语句内所有时间戳相同,维护数据一致性。 |
| 记录日志,反映真实的调用顺序和间隔 | SYSDATE() |
能捕捉到毫秒级差异,适合调试和性能分析。 |
| WHERE 条件中过滤当前时间的数据 | NOW() |
确定性常量,可利用索引,性能更佳。 |
| 主从复制环境下的写操作 | NOW() |
避免不确定性导致的复制问题。 |
| 测量存储过程内部代码块的执行耗时 | SYSDATE() |
在不同位置调用,计算差值可以获得真实流逝时间。 |
七、总结:一句话记住它们的区别
NOW():在语句开始时一次性赋值,语句内恒定,是稳定的时间常量。SYSDATE():在函数每次被调用时返回实时系统时间,是动态的时间探针。
掌握这一区别后,你可以更精准地控制 SQL 中的时间逻辑。下次遇到需要当前时间的场景,先问自己一句:“我需要的是语句级别的统一快照,还是时刻变化的绝对实时?” 答案自明。
延伸练习: 尝试开启 MySQL 的通用查询日志(General Log),观察同一个存储过程中多次调用 NOW() 和 SYSDATE() 时,日志中记录的具体语句和执行时间,加深理解。