一、日期函数的基本概念

在SQLServer中,我们经常需要获取当前日期和时间,这时候就会用到几个常用的日期函数。这些函数看起来功能相似,但实际上在精度和性能上有着不小的差异。

让我们先来看看这三个函数的基本定义:

  1. GETDATE() - 返回当前数据库系统的日期和时间,精度为3.33毫秒
  2. SYSDATETIME() - 返回当前数据库系统的日期和时间,精度为100纳秒
  3. CURRENT_TIMESTAMP - ANSI SQL标准等价于GETDATE()的函数
-- 示例1:基本函数使用对比
SELECT 
    GETDATE() AS 'GETDATE示例',          -- 返回当前日期时间,精度3.33ms
    SYSDATETIME() AS 'SYSDATETIME示例', -- 返回更高精度的当前日期时间
    CURRENT_TIMESTAMP AS 'CURRENT_TIMESTAMP示例' -- ANSI标准函数,等同于GETDATE()

从上面的示例可以看出,虽然这三个函数都能返回当前时间,但SYSDATETIME()明显提供了更高的精度。那么,这是否意味着我们应该总是使用SYSDATETIME()呢?事情并没有这么简单。

二、性能对比与内部实现

要真正理解这些函数的差异,我们需要深入了解一下它们的内部实现和性能特点。

-- 示例2:性能测试对比
DECLARE @i INT = 0
DECLARE @count INT = 1000000
DECLARE @startTime DATETIME2 = SYSDATETIME()

-- 测试GETDATE()性能
WHILE @i < @count
BEGIN
    DECLARE @temp DATETIME = GETDATE()
    SET @i = @i + 1
END

DECLARE @endTimeGetDate DATETIME2 = SYSDATETIME()

-- 测试SYSDATETIME()性能
SET @i = 0
WHILE @i < @count
BEGIN
    DECLARE @temp2 DATETIME2 = SYSDATETIME()
    SET @i = @i + 1
END

DECLARE @endTimeSysDateTime DATETIME2 = SYSDATETIME()

-- 测试CURRENT_TIMESTAMP性能
SET @i = 0
WHILE @i < @count
BEGIN
    DECLARE @temp3 DATETIME = CURRENT_TIMESTAMP
    SET @i = @i + 1
END

DECLARE @endTimeCurrentTimestamp DATETIME2 = SYSDATETIME()

-- 输出结果
SELECT 
    DATEDIFF(MILLISECOND, @startTime, @endTimeGetDate) AS 'GETDATE耗时(ms)',
    DATEDIFF(MILLISECOND, @endTimeGetDate, @endTimeSysDateTime) AS 'SYSDATETIME耗时(ms)',
    DATEDIFF(MILLISECOND, @endTimeSysDateTime, @endTimeCurrentTimestamp) AS 'CURRENT_TIMESTAMP耗时(ms)'

通过这个性能测试,我们可以发现一些有趣的现象。在我的测试环境中,GETDATE()和CURRENT_TIMESTAMP的性能几乎相同(因为它们本质上是同一个函数),而SYSDATETIME()则稍微慢一些,因为它需要获取更高精度的时间。

三、数据类型与精度影响

这些函数返回的数据类型也不尽相同,这会影响到我们如何使用它们以及它们的存储需求。

-- 示例3:返回数据类型对比
SELECT 
    SQL_VARIANT_PROPERTY(GETDATE(), 'BaseType') AS 'GETDATE类型',          -- 返回datetime
    SQL_VARIANT_PROPERTY(SYSDATETIME(), 'BaseType') AS 'SYSDATETIME类型', -- 返回datetime2
    SQL_VARIANT_PROPERTY(CURRENT_TIMESTAMP, 'BaseType') AS 'CURRENT_TIMESTAMP类型' -- 返回datetime

datetime和datetime2类型的主要区别包括:

  1. 日期范围:datetime是1753-1-1到9999-12-31,datetime2是0001-1-1到9999-12-31
  2. 精度:datetime精度为3.33毫秒,datetime2精度可达100纳秒
  3. 存储空间:datetime固定8字节,datetime2根据精度6-8字节
-- 示例4:精度对比演示
SELECT 
    GETDATE() AS '标准精度',  -- 毫秒级精度
    SYSDATETIME() AS '高精度' -- 纳秒级精度

-- 更直观的精度对比
DECLARE @dt DATETIME = GETDATE()
DECLARE @dt2 DATETIME2 = SYSDATETIME()

WAITFOR DELAY '00:00:00.003' -- 等待3毫秒

SELECT 
    DATEDIFF(MILLISECOND, @dt, GETDATE()) AS 'GETDATE时间差(ms)',
    DATEDIFF(NANOSECOND, @dt2, SYSDATETIME()) AS 'SYSDATETIME时间差(ns)'

四、应用场景与最佳实践

了解了这些函数的特点后,我们来看看在实际开发中应该如何选择使用它们。

1. 常规业务场景

对于大多数业务系统,如订单创建时间、用户注册时间等,GETDATE()或CURRENT_TIMESTAMP完全够用。

-- 示例5:订单表创建
CREATE TABLE Orders (
    OrderID INT PRIMARY KEY,
    CustomerID INT,
    OrderAmount DECIMAL(10,2),
    OrderDate DATETIME DEFAULT GETDATE(),  -- 使用GETDATE作为默认值
    LastUpdated DATETIME DEFAULT CURRENT_TIMESTAMP -- 也可以使用CURRENT_TIMESTAMP
)

-- 插入数据时不指定日期字段,自动使用默认值
INSERT INTO Orders (OrderID, CustomerID, OrderAmount)
VALUES (1, 1001, 99.99)

SELECT * FROM Orders

2. 高精度需求场景

对于需要高精度时间戳的场景,如金融交易、科学实验数据记录等,应该使用SYSDATETIME()。

-- 示例6:高频交易记录表
CREATE TABLE StockTrades (
    TradeID UNIQUEIDENTIFIER DEFAULT NEWID(),
    StockCode VARCHAR(10),
    TradePrice DECIMAL(10,4),
    TradeVolume INT,
    TradeTime DATETIME2(7) DEFAULT SYSDATETIME(), -- 使用最高精度的datetime2
    PRIMARY KEY (TradeID, TradeTime)
)

-- 插入交易数据
INSERT INTO StockTrades (StockCode, TradePrice, TradeVolume)
VALUES ('AAPL', 145.32, 100)

-- 查看交易时间精度
SELECT 
    TradeTime,
    DATEPART(NANOSECOND, TradeTime) AS '纳秒部分'
FROM StockTrades

3. 跨数据库兼容性考虑

如果你的应用可能需要迁移到其他数据库系统,或者需要编写可移植的SQL代码,那么使用CURRENT_TIMESTAMP是更好的选择,因为它是ANSI SQL标准。

-- 示例7:兼容性表设计
CREATE TABLE AuditLog (
    LogID INT IDENTITY PRIMARY KEY,
    ActionType VARCHAR(20),
    ActionDetails NVARCHAR(MAX),
    ActionTime DATETIME DEFAULT CURRENT_TIMESTAMP, -- 使用ANSI标准函数
    UserName VARCHAR(50)
)

-- 插入审计日志
INSERT INTO AuditLog (ActionType, ActionDetails, UserName)
VALUES ('LOGIN', '用户登录系统', 'admin')

SELECT * FROM AuditLog

五、注意事项与常见问题

在使用这些日期函数时,有几个常见的陷阱需要注意:

  1. 函数调用时机:在同一个SQL语句中多次调用这些函数可能会返回不同的值
-- 示例8:函数调用时机问题
SELECT 
    GETDATE() AS '第一次调用',
    GETDATE() AS '第二次调用',  -- 可能会有微小差异
    SYSDATETIME() AS '高精度第一次',
    SYSDATETIME() AS '高精度第二次' -- 差异会更明显
  1. 默认值的行为:在表定义中使用这些函数作为默认值时,它们只会在插入时计算一次
-- 示例9:默认值行为演示
CREATE TABLE TestTiming (
    ID INT IDENTITY PRIMARY KEY,
    CreatedTime DATETIME DEFAULT GETDATE(),
    ModifiedTime DATETIME DEFAULT GETDATE()
)

-- 插入数据
INSERT INTO TestTiming DEFAULT VALUES

-- 等待5秒
WAITFOR DELAY '00:00:05'

-- 更新数据
UPDATE TestTiming SET ModifiedTime = GETDATE() WHERE ID = 1

-- 查看结果
SELECT * FROM TestTiming  -- CreatedTime和ModifiedTime初始相同,更新后ModifiedTime变化
  1. 时区考虑:这些函数返回的是服务器的时间,不考虑客户端时区
-- 示例10:时区问题演示
-- 假设服务器在UTC时区,客户端在UTC+8时区
SELECT 
    GETDATE() AS '服务器时间',
    GETUTCDATE() AS 'UTC时间' -- 当需要考虑时区时可以使用GETUTCDATE()

六、高级应用场景

对于更复杂的应用场景,我们可以结合这些日期函数和其他SQLServer功能来实现强大的功能。

1. 自动更新修改时间

-- 示例11:自动更新修改时间
CREATE TABLE Products (
    ProductID INT PRIMARY KEY,
    ProductName NVARCHAR(100),
    Price DECIMAL(10,2),
    CreatedDate DATETIME DEFAULT CURRENT_TIMESTAMP,
    ModifiedDate DATETIME DEFAULT CURRENT_TIMESTAMP
)

-- 创建触发器自动更新ModifiedDate
CREATE TRIGGER trg_UpdateProductDate
ON Products
AFTER UPDATE
AS
BEGIN
    UPDATE Products
    SET ModifiedDate = GETDATE()
    FROM Products p
    INNER JOIN inserted i ON p.ProductID = i.ProductID
END

-- 测试触发器
INSERT INTO Products (ProductID, ProductName, Price)
VALUES (1, '笔记本电脑', 5999.00)

-- 等待一会
WAITFOR DELAY '00:00:03'

-- 更新产品价格
UPDATE Products SET Price = 5499.00 WHERE ProductID = 1

-- 查看结果
SELECT * FROM Products  -- 可以看到CreatedDate和ModifiedDate不同

2. 性能监控与统计

-- 示例12:存储过程执行时间监控
CREATE PROCEDURE usp_GetCustomerOrders
    @CustomerID INT
AS
BEGIN
    DECLARE @StartTime DATETIME2 = SYSDATETIME()
    
    -- 业务逻辑
    SELECT 
        o.OrderID,
        o.OrderDate,
        o.TotalAmount,
        p.ProductName,
        od.Quantity
    FROM Orders o
    JOIN OrderDetails od ON o.OrderID = od.OrderID
    JOIN Products p ON od.ProductID = p.ProductID
    WHERE o.CustomerID = @CustomerID
    
    -- 记录执行时间
    DECLARE @EndTime DATETIME2 = SYSDATETIME()
    DECLARE @DurationInMicroseconds INT = DATEDIFF(MICROSECOND, @StartTime, @EndTime)
    
    -- 插入性能日志
    INSERT INTO PerformanceLog (ProcedureName, ExecutionTime, LogTime)
    VALUES ('usp_GetCustomerOrders', @DurationInMicroseconds, GETDATE())
END

3. 批次处理与超时控制

-- 示例13:批次处理超时控制
CREATE PROCEDURE usp_ProcessLargeBatch
    @BatchSize INT = 1000,
    @TimeoutSeconds INT = 30
AS
BEGIN
    DECLARE @StartTime DATETIME = GETDATE()
    DECLARE @Processed INT = 0
    DECLARE @Continue BIT = 1
    
    WHILE @Continue = 1
    BEGIN
        -- 处理一批数据
        UPDATE TOP (@BatchSize) UnprocessedData
        SET Status = 'PROCESSED',
            ProcessedTime = GETDATE()
        WHERE Status = 'PENDING'
        
        SET @Processed = @Processed + @@ROWCOUNT
        
        -- 检查是否还有数据需要处理
        IF NOT EXISTS (SELECT 1 FROM UnprocessedData WHERE Status = 'PENDING')
            SET @Continue = 0
            
        -- 检查是否超时
        IF DATEDIFF(SECOND, @StartTime, GETDATE()) >= @TimeoutSeconds
        BEGIN
            RAISERROR('处理超时,已处理%d条记录', 10, 1, @Processed)
            SET @Continue = 0
        END
    END
    
    SELECT @Processed AS '已处理记录数'
END

七、总结与建议

经过以上分析和示例演示,我们可以得出以下结论:

  1. 对于大多数常规业务场景,GETDATE()和CURRENT_TIMESTAMP是最佳选择,它们性能好且完全够用
  2. 需要高精度时间戳的场景应该使用SYSDATETIME(),但要注意它的性能开销稍大
  3. 考虑跨数据库兼容性时,优先使用CURRENT_TIMESTAMP
  4. 在表设计中,datetime2(7)配合SYSDATETIME()可以提供最高的时间精度
  5. 对于需要自动维护创建/修改时间的表,结合默认值和触发器是很好的解决方案

最后,选择哪个日期函数取决于你的具体需求。在大多数情况下,简单就是美,不需要过度追求高精度。只有在确实需要高精度时间戳时,才应该承担SYSDATETIME()带来的额外开销。