一、日期函数的基本概念
在SQLServer中,我们经常需要获取当前日期和时间,这时候就会用到几个常用的日期函数。这些函数看起来功能相似,但实际上在精度和性能上有着不小的差异。
让我们先来看看这三个函数的基本定义:
- GETDATE() - 返回当前数据库系统的日期和时间,精度为3.33毫秒
- SYSDATETIME() - 返回当前数据库系统的日期和时间,精度为100纳秒
- 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类型的主要区别包括:
- 日期范围:datetime是1753-1-1到9999-12-31,datetime2是0001-1-1到9999-12-31
- 精度:datetime精度为3.33毫秒,datetime2精度可达100纳秒
- 存储空间: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
五、注意事项与常见问题
在使用这些日期函数时,有几个常见的陷阱需要注意:
- 函数调用时机:在同一个SQL语句中多次调用这些函数可能会返回不同的值
-- 示例8:函数调用时机问题
SELECT
GETDATE() AS '第一次调用',
GETDATE() AS '第二次调用', -- 可能会有微小差异
SYSDATETIME() AS '高精度第一次',
SYSDATETIME() AS '高精度第二次' -- 差异会更明显
- 默认值的行为:在表定义中使用这些函数作为默认值时,它们只会在插入时计算一次
-- 示例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变化
- 时区考虑:这些函数返回的是服务器的时间,不考虑客户端时区
-- 示例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
七、总结与建议
经过以上分析和示例演示,我们可以得出以下结论:
- 对于大多数常规业务场景,GETDATE()和CURRENT_TIMESTAMP是最佳选择,它们性能好且完全够用
- 需要高精度时间戳的场景应该使用SYSDATETIME(),但要注意它的性能开销稍大
- 考虑跨数据库兼容性时,优先使用CURRENT_TIMESTAMP
- 在表设计中,datetime2(7)配合SYSDATETIME()可以提供最高的时间精度
- 对于需要自动维护创建/修改时间的表,结合默认值和触发器是很好的解决方案
最后,选择哪个日期函数取决于你的具体需求。在大多数情况下,简单就是美,不需要过度追求高精度。只有在确实需要高精度时间戳时,才应该承担SYSDATETIME()带来的额外开销。
评论