
1. 为什么“查时间”这个简单操作值得深究你可能觉得在数据库里查询当前时间不就是一句SELECT NOW()或者SELECT SYSDATE的事吗有什么好写的我刚开始做开发的时候也是这么想的直到后来踩了几个不大不小的坑。比如一个跨时区的订单系统因为开发环境和生产环境数据库的时区设置不同导致报表里的“下单时间”总是对不上排查了半天才发现是NOW()函数返回的是数据库服务器本地时间而非我们业务逻辑里约定的 UTC 时间。又比如在做数据同步或定时任务时需要获取一个精确到毫秒甚至微秒的时间戳作为版本号或流水号不同数据库的函数精度和支持格式天差地别。所以“查询当前时间”远不止是一个简单的函数调用。它背后涉及到数据库的时区处理机制、时间精度、函数在不同上下文中的行为如在存储过程、触发器里以及不同数据库方言的兼容性写法。无论是刚入门的新手还是需要处理多数据库兼容架构的老手理清这些细节都能避免很多潜在的坑。今天我就结合 MySQL、Oracle 和 SQL Server 这三大主流数据库把“查时间”这件事掰开揉碎了讲清楚不仅告诉你怎么写更告诉你为什么这么写以及在不同场景下该怎么选。2. MySQL 中获取当前时间的多种姿势与核心差异MySQL 提供了好几个用于获取当前日期时间的函数它们看起来功能相似但在返回值的类型、是否包含时区信息以及是否随 SQL 语句或事务变化上有着本质的区别。用错了地方轻则数据不准重则逻辑混乱。2.1 基础三剑客NOW(), CURDATE(), CURTIME()这是最常用的一组函数它们的含义非常直观。NOW() 返回当前的日期和时间。这是你最常用的函数。它返回一个DATETIME类型的值格式类似于‘2023-10-27 14:30:15‘。它的精度取决于 MySQL 的版本和设置最高可以到微秒6位小数例如‘2023-10-27 14:30:15.123456‘。SELECT NOW(); -- 输出: 2023-10-27 14:30:15CURDATE() 只返回当前日期时间部分为‘00:00:00‘。返回类型是DATE。SELECT CURDATE(); -- 输出: 2023-10-27CURTIME() 只返回当前时间日期部分为当前日期。返回类型是TIME。同样支持微秒精度。SELECT CURTIME(); -- 输出: 14:30:15实操心得一关于NOW()的“不变性”在一个 SQL 语句中多次调用NOW()返回的值是相同的。例如SELECT NOW(), SLEEP(2), NOW();两个NOW()输出相同的时间不会因为SLEEP了2秒而改变。这是因为NOW()的值在语句开始执行时就被确定了。这个特性在需要保持时间一致的场景下很有用比如记录一条数据的创建时间和更新时间你希望它们相同。2.2 高精度与低开销SYSDATE() vs. NOW()SYSDATE()是另一个容易和NOW()混淆的函数。它同样返回当前的日期时间但关键区别在于SYSDATE()返回的是函数执行时刻的实时时间。SELECT NOW(), SLEEP(2), NOW(), SYSDATE(), SLEEP(2), SYSDATE();执行这段代码你会发现第一个和第二个NOW()的值相同但第一个和第二个SYSDATE()的值会相差大约2秒。为什么这个区别很重要主从复制与日志恢复SYSDATE()是非确定性函数它在二进制日志binlog中是不安全的。在主从复制环境下如果 SQL 语句里使用了SYSDATE()从库执行时可能会产生与主库不同的时间值导致数据不一致。因此很多公司的数据库规范会明确禁止在业务 SQL 中使用SYSDATE()。性能影响因为SYSDATE()无法被优化且破坏了查询缓存所以它的性能比NOW()稍差。注意在绝大多数业务场景下请优先使用NOW()。除非你明确需要获取语句执行过程中某个精确时刻的时间这种场景极少否则不要使用SYSDATE()。2.3 时间戳函数CURRENT_TIMESTAMP 与 UNIX_TIMESTAMP()这两个函数用于获取时间戳但在表示形式上完全不同。CURRENT_TIMESTAMP或CURRENT_TIMESTAMP() 这是NOW()的同义词。在 MySQL 中它们完全等价。它返回标准的DATETIME格式。在定义表结构时我们常用它作为TIMESTAMP类型字段的默认值。CREATE TABLE orders ( id INT PRIMARY KEY, order_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP );UNIX_TIMESTAMP() 返回自 ‘1970-01-01 00:00:00‘ UTC 以来的秒数无参数时也就是 Unix 时间戳。它是一个整型数字非常适合用于计算时间间隔或作为缓存键的一部分。SELECT UNIX_TIMESTAMP(); -- 输出: 1698412215 SELECT UNIX_TIMESTAMP(‘2023-10-27 14:30:15‘); -- 将日期时间转为时间戳与NOW()的转换你可以组合使用它们。SELECT UNIX_TIMESTAMP(NOW());就能得到当前的秒级时间戳。如果需要毫秒级时间戳可以这样SELECT UNIX_TIMESTAMP(NOW(6)) * 1000;。2.4 时区处理的关键UTC_TIME() 与系统变量时区问题是数据库时间处理中最常见的坑之一。MySQL 有两套时区设置系统时区 服务器操作系统所在的时区。会话时区 每个数据库连接自己的时区默认继承自系统时区但可以修改。NOW()、CURTIME()等函数返回的是会话时区下的时间。为了规避时区混乱最佳实践是在业务中统一使用 UTC 时间进行存储和计算。MySQL 提供了对应的 UTC 时间函数UTC_DATE(): 返回当前 UTC 日期。UTC_TIME(): 返回当前 UTC 时间。UTC_TIMESTAMP(): 返回当前 UTC 日期和时间。-- 假设会话时区是东八区 (Asia/Shanghai) SELECT NOW(), UTC_TIMESTAMP(); -- 输出可能为: -- NOW(): 2023-10-27 22:30:15 -- UTC_TIMESTAMP(): 2023-10-27 14:30:15如何查看和设置时区-- 查看全局和会话时区 SELECT global.time_zone, session.time_zone; -- 常见结果: ‘SYSTEM‘, ‘SYSTEM‘ 或 ‘08:00‘ -- 设置当前会话的时区只影响当前连接 SET SESSION time_zone ‘08:00‘; -- 东八区 SET SESSION time_zone ‘UTC‘; SET SESSION time_zone ‘America/New_York‘;强烈建议在应用程序连接数据库后首先执行SET SESSION time_zone ‘08:00‘;或你所在的时区确保所有时间函数的行为符合预期。对于需要全球服务的应用应在数据库层统一存储 UTC 时间在应用层根据用户时区进行转换。3. Oracle 的时间世界SYSDATE、SYSTIMESTAMP 与会话时区Oracle 的时间函数体系与 MySQL 有相似之处但更强调精度和时区信息。其核心函数的行为与会话的NLS_DATE_FORMAT等参数紧密相关这也是容易混淆的地方。3.1 核心函数 SYSDATE 与 SYSTIMESTAMPSYSDATE 这是 Oracle 中最常用的获取当前日期时间的函数。它返回数据库服务器操作系统时区的当前日期和时间数据类型是DATE。注意Oracle 的DATE类型包含日期和时间部分精度到秒没有时区信息。SELECT SYSDATE FROM DUAL; -- 输出: 27-OCT-23 (具体格式受 NLS_DATE_FORMAT 影响如 ‘YYYY-MM-DD HH24:MI:SS‘)SYSTIMESTAMP 返回数据库服务器操作系统时区的当前日期和时间数据类型是TIMESTAMP WITH TIME ZONE。它包含了时区偏移量如08:00和更高的精度默认微秒。SELECT SYSTIMESTAMP FROM DUAL; -- 输出: 27-OCT-23 02.30.15.123456 PM 08:00关键选择什么时候用 SYSDATE什么时候用 SYSTIMESTAMP用SYSDATE当你只需要日期和时间不关心时区且秒级精度足够时。例如记录一个纯本地操作的日志时间。用SYSTIMESTAMP强烈推荐在大多数新项目中使用。因为它携带时区信息精度更高在涉及跨时区计算、数据同步或需要高精度时间戳如金融交易的场景下它是唯一正确的选择。存储TIMESTAMP WITH TIME ZONE类型的数据可以让你在后续查询时毫无歧义地进行时区转换。3.2 会话相关的时间函数CURRENT_DATE 与 CURRENT_TIMESTAMP这两个函数的行为取决于当前会话的时区而不是数据库服务器的时区。这对于分布式应用或用户遍布全球的系统至关重要。CURRENT_DATE 返回当前会话时区下的当前日期类型为DATE。时间部分为当天的午夜。CURRENT_TIMESTAMP 返回当前会话时区下的当前日期和时间类型为TIMESTAMP WITH TIME ZONE。它的精度可以指定如CURRENT_TIMESTAMP(6)表示微秒精度。-- 首先更改会话时区 ALTER SESSION SET TIME_ZONE ‘America/Los_Angeles‘; SELECT SYSDATE, CURRENT_DATE, SYSTIMESTAMP, CURRENT_TIMESTAMP FROM DUAL;你会看到SYSDATE/SYSTIMESTAMP显示的是数据库服务器所在地时间比如上海而CURRENT_DATE/CURRENT_TIMESTAMP显示的是洛杉矶时间。应用场景假设你的数据库部署在东京但你的用户在美国。使用CURRENT_TIMESTAMP来记录用户的“操作时间”得到的就是用户本地时间这比存储东京时间再去做转换要直观和准确得多。3.3 时区转换函数FROM_TZ 与 AT TIME ZONEOracle 提供了强大的时区转换功能。FROM_TZ 将一个TIMESTAMP数据与一个给定时区结合转换成TIMESTAMP WITH TIME ZONE。SELECT FROM_TZ(TIMESTAMP ‘2023-10-27 14:30:15‘, ‘Asia/Shanghai‘) FROM DUAL;AT TIME ZONE 将一个TIMESTAMP WITH TIME ZONE数据转换到另一个时区。-- 将当前时间带时区转换为 UTC 时间 SELECT CURRENT_TIMESTAMP AT TIME ZONE ‘UTC‘ FROM DUAL; -- 将特定时间转换 SELECT TIMESTAMP ‘2023-10-27 14:30:15 Asia/Shanghai‘ AT TIME ZONE ‘America/New_York‘ FROM DUAL;3.4 格式化输出与 NLS 参数Oracle 默认的日期显示格式可能不符合你的习惯这由NLS_DATE_FORMAT等会话参数控制。-- 查看当前会话的日期格式 SELECT VALUE FROM NLS_SESSION_PARAMETERS WHERE PARAMETER ‘NLS_DATE_FORMAT‘; -- 输出可能是: ‘DD-MON-RR‘ -- 临时更改当前会话的显示格式 ALTER SESSION SET NLS_DATE_FORMAT ‘YYYY-MM-DD HH24:MI:SS‘; ALTER SESSION SET NLS_TIMESTAMP_FORMAT ‘YYYY-MM-DD HH24:MI:SS.FF6‘; ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT ‘YYYY-MM-DD HH24:MI:SS.FF6 TZR‘; -- 现在查询输出就是新格式了 SELECT SYSDATE, SYSTIMESTAMP FROM DUAL;实操心得二在客户端工具中设置默认格式为了避免每次连接都执行ALTER SESSION你可以在常用的客户端工具如 SQL Developer、DBeaver的连接属性或初始化脚本中设置这些NLS参数。对于程序代码如 JDBC可以在连接字符串中设置v$session的相应参数。4. SQL Server 的 GETDATE、SYSDATETIME 与时区考量SQL Server 的时间函数命名非常直接其核心区别在于精度和是否包含时区偏移量。从 SQL Server 2008 开始引入了更高精度的数据类型和函数。4.1 标准函数 GETDATE() 与 GETUTCDATE()GETDATE() 返回 SQL Server 实例所在计算机的系统日期和时间。返回类型是DATETIME精度约为 3.33 毫秒。SELECT GETDATE(); -- 输出: 2023-10-27 14:30:15.123这是最通用、最常用的函数适用于绝大多数不需要微秒级精度的场景。GETUTCDATE() 返回当前的 UTC 日期和时间。返回类型也是DATETIME。SELECT GETUTCDATE();重要建议对于可能部署在不同地区的系统或者需要与其它 UTC 时间的系统交互的场景使用GETUTCDATE()存储时间戳是更好的实践可以避免时区转换的麻烦。4.2 高精度函数 SYSDATETIME() 与 SYSUTCDATETIME()随着对时间精度要求的提高SQL Server 2008 引入了新的数据类型DATETIME2,DATETIMEOFFSET和对应的高精度函数。SYSDATETIME() 返回计算机的日期和时间返回类型是DATETIME2(7)精度为 100 纳秒。SELECT SYSDATETIME(); -- 输出: 2023-10-27 14:30:15.1234567SYSUTCDATETIME() 返回当前的 UTC 日期和时间返回类型是DATETIME2(7)。SELECT SYSUTCDATETIME();SYSDATETIMEOFFSET() 返回计算机的日期和时间并包含时区偏移量。返回类型是DATETIMEOFFSET(7)。这是功能最全的函数。SELECT SYSDATETIMEOFFSET(); -- 输出: 2023-10-27 14:30:15.1234567 08:00如何选择如果业务只关心到秒或毫秒且无跨时区需求用GETDATE()或GETUTCDATE()即可。如果需要高精度如性能测量、科学计算用SYSDATETIME()或SYSUTCDATETIME()。如果系统服务于全球用户需要明确知道时间所属的时区必须使用SYSDATETIMEOFFSET()。4.3 时区转换AT TIME ZONE 子句从 SQL Server 2016 开始引入了AT TIME ZONE子句极大地简化了时区转换。-- 将本地时间转换为 UTC 时间 SELECT GETDATE() AT TIME ZONE ‘China Standard Time‘ AT TIME ZONE ‘UTC‘; -- 解释首先声明 GETDATE() 是‘中国标准时间‘然后转换到‘UTC‘。 -- 直接转换带时区偏移的时间 SELECT SYSDATETIMEOFFSET() AT TIME ZONE ‘Pacific Standard Time‘;查看支持的时区名称SELECT * FROM sys.time_zone_info;在转换时需要使用上表中name列的值如‘China Standard Time‘,‘UTC‘。4.4 日期部分提取与格式化虽然查询当前时间返回的是完整值但经常需要提取其中的年、月、日等部分。SQL Server 推荐使用DATEPART和DATENAME函数或者更现代的YEAR(),MONTH(),DAY()函数。SELECT GETDATE() AS CurrentTime, DATEPART(YEAR, GETDATE()) AS YearPart, DATENAME(MONTH, GETDATE()) AS MonthName, -- 返回‘October‘ YEAR(GETDATE()) AS YearShort, FORMAT(GETDATE(), ‘yyyy-MM-dd HH:mm:ss.fff‘) AS FormattedString; -- 格式化输出关于FORMAT函数FORMAT()函数非常强大可以输出任何自定义格式的字符串但它的性能开销相对较大。在对大量数据进行格式化时需谨慎使用。对于简单的格式使用CONVERT函数通常是更高效的选择。5. 实战场景下的函数选择与避坑指南了解了各个数据库的函数后我们来看几个具体的实战场景分析该如何选择以及有哪些坑需要避开。5.1 场景一记录数据行的创建与更新时间这是最常见的需求。通常我们在表设计中会有create_time和update_time字段。MySQL:CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50), -- DATETIME 或 TIMESTAMP 类型均可TIMESTAMP 有自动时区转换特性 create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );注意TIMESTAMP类型范围较小1970-2038且其显示依赖于时区设置。如果存储的时间可能早于1970或晚于2038或者你希望时间值完全不受时区转换影响请使用DATETIME。Oracle: Oracle 没有内置的ON UPDATE语法。通常需要借助触发器。CREATE TABLE user ( id NUMBER PRIMARY KEY, username VARCHAR2(50), create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, update_time TIMESTAMP ); CREATE OR REPLACE TRIGGER trg_user_update BEFORE UPDATE ON user FOR EACH ROW BEGIN :NEW.update_time : CURRENT_TIMESTAMP; END;建议create_time使用DEFAULT CURRENT_TIMESTAMPupdate_time通过触发器更新。字段类型推荐TIMESTAMP以获得更高精度。SQL Server:CREATE TABLE user ( id INT PRIMARY KEY IDENTITY(1,1), username NVARCHAR(50), create_time DATETIME2 DEFAULT SYSDATETIME(), update_time DATETIME2 ); -- 同样需要触发器来更新 update_time选择对于新表建议使用DATETIME2代替旧的DATETIME以获得更大的范围和更高的精度。SYSDATETIME()比GETDATE()精度更高。5.2 场景二生成唯一、有序的业务流水号或版本号在分布式系统中经常需要一种全局唯一且大致有序的 ID利用时间戳是一个好方法。方案使用高精度时间戳如毫秒或微秒级加上随机数或序列号。MySQL:-- 毫秒级时间戳13位 SELECT CONCAT(UNIX_TIMESTAMP(NOW(3)) * 1000, LPAD(FLOOR(RAND()*1000), 3, ‘0‘)) AS biz_no; -- 或者使用微秒部分 SELECT REPLACE(UNIX_TIMESTAMP(NOW(6)), ‘.‘, ‘‘) AS micro_timestamp;Oracle:-- 获取微秒级时间戳字符串 SELECT TO_CHAR(SYSTIMESTAMP, ‘YYYYMMDDHH24MISSFF6‘) FROM DUAL; -- 结合序列 SELECT TO_CHAR(SYSTIMESTAMP, ‘YYYYMMDDHH24MISSFF6‘) || LPAD(seq_biz.nextval, 6, ‘0‘) FROM DUAL;SQL Server:-- 获取微秒级时间戳字符串 SELECT FORMAT(SYSDATETIME(), ‘yyyyMMddHHmmssffffff‘); -- 或者使用二进制转换性能更好 SELECT CONVERT(VARCHAR(30), SYSDATETIME(), 121); -- 格式: 2023-10-27 14:30:15.1234567避坑点在极高并发下单纯的时间戳即使到微秒仍可能重复。生产环境中通常需要结合机器ID、进程ID或数据库序列来保证绝对唯一性。5.3 场景三跨数据库兼容的 SQL 写法如果你在开发一个需要支持多种数据库的中间件或应用你需要一个抽象层来处理这些差异。策略在应用代码中不要直接写数据库原生的时间函数而是通过配置文件或代码映射来获取。// 伪代码示例 public interface TimeFunctionProvider { String getCurrentTimestampSQL(); } public class MySQLTimeProvider implements TimeFunctionProvider { Override public String getCurrentTimestampSQL() { return “NOW()“; } } public class OracleTimeProvider implements TimeFunctionProvider { Override public String getCurrentTimestampSQL() { return “CURRENT_TIMESTAMP“; } } public class SQLServerTimeProvider implements TimeFunctionProvider { Override public String getCurrentTimestampSQL() { return “SYSDATETIME()“; } }在 SQL 映射文件如 MyBatis中可以使用动态标签来区分。select id“getCurrentTime“ if test“_databaseId ‘mysql‘“ SELECT NOW() /if if test“_databaseId ‘oracle‘“ SELECT CURRENT_TIMESTAMP FROM DUAL /if if test“_databaseId ‘sqlserver‘“ SELECT SYSDATETIME() /if /select5.4 常见陷阱与性能考量函数在索引中的使用在WHERE子句中对日期字段使用函数如WHERE DATE(create_time) ‘2023-10-27‘会导致数据库无法使用该字段上的索引引发全表扫描。正确的做法是使用范围查询-- 错误无法利用索引 SELECT * FROM orders WHERE DATE(order_time) ‘2023-10-27‘; -- 正确可以利用 order_time 上的索引 SELECT * FROM orders WHERE order_time ‘2023-10-27 00:00:00‘ AND order_time ‘2023-10-28 00:00:00‘;默认值的行为CURRENT_TIMESTAMP在作为默认值时在 MySQL 和 SQL Server 中通常指的是行插入时的时间。但在某些上下文中如 Oracle 的触发器可能需要明确使用CURRENT_TIMESTAMP函数来赋值。精度丢失将高精度时间类型如DATETIME2(7),TIMESTAMP(6)赋值给低精度类型如DATETIME或者在高并发下用低精度函数生成流水号会导致数据冲突或精度丢失。时区一致性确保应用服务器、数据库服务器以及数据库连接会话的时区设置一致。最稳妥的方案是数据库始终使用 UTC 时间存储用UTC_TIMESTAMP(),SYSUTCDATETIME()等应用层在展示时根据用户所在时区进行转换。SYSDATE在 MySQL 中的复制风险再次强调在 MySQL 主从复制中避免在业务 SQL 中使用SYSDATE()优先使用NOW()。6. 高级话题在存储过程、触发器和事件中的时间处理当时间函数出现在数据库的程序性对象中时其行为有一些特别之处。6.1 在存储过程和函数中在存储过程中时间函数的行为通常与在普通 SQL 语句中一致。但需要注意自治事务中的时间。在 Oracle 的自治事务块中SYSDATE仍然是主事务开始的时间还是自治事务开始的时间答案是它仍然是调用数据库服务器操作系统的时间不受事务边界影响。但如果你在过程中大量调用SYSDATE而这个过程又被频繁执行可能会对性能有细微影响因为每次调用都是一次系统调用。6.2 在触发器中触发器中的时间函数用于记录数据变更的“当下”时刻。MySQL在BEFORE INSERT或BEFORE UPDATE触发器中使用NOW()来设置字段值这个NOW()的值在触发器执行时确定并且在该条语句的后续操作中保持不变。Oracle在行级触发器FOR EACH ROW中使用:NEW.column : CURRENT_TIMESTAMP;来赋值。注意在触发器内部直接使用CURRENT_TIMESTAMP而不是SYSDATE通常更能反映“操作时刻”尤其是当会话时区与服务器时区不同时。SQL Server在INSTEAD OF或AFTER触发器中可以使用GETDATE()或SYSDATETIME()。但要注意触发器内的时间与导致触发器触发的那条语句的时间可能有极小的延迟。一个关键点在 Oracle 和 SQL Server 中如果触发器被递归调用例如在触发器中又修改了本表那么每次触发器执行时获取的“当前时间”都是新的这可能导致意想不到的结果。6.3 在定时任务或事件中数据库自身的定时任务如 MySQL 的EVENTOracle 的DBMS_SCHEDULERSQL Server 的SQL Server Agent Job中也需要获取时间来判断是否执行或记录日志。MySQL EVENTCREATE EVENT my_daily_event ON SCHEDULE EVERY 1 DAY STARTS CURDATE() INTERVAL 1 DAY DO BEGIN -- 事件体内的 NOW() 是事件执行时刻的时间 INSERT INTO audit_log (event_time, message) VALUES (NOW(), ‘Daily event executed.‘); END确保时间一致性在复杂的定时任务逻辑中如果整个任务需要基于同一个“开始时间”进行应该在任务最开始用一个变量保存这个时间点然后在后续逻辑中复用这个变量而不是多次调用时间函数。