存储过程参数必须用datetime2而非datetime,否则因精度和范围差异导致隐式转换使索引失效;日期条件应避免函数包裹列,改用范围查询并预先计算边界变量。

存储过程参数必须用 datetime2,别用 datetime
用 datetime 声明 @start_date 和 @end_date 是索引失效的高频原因。它精度只有 3.33ms,范围窄(1753–9999),更关键的是:当表中日期列是 datetime2(7),而参数是 datetime,SQL Server 会把整列隐式转成 datetime 再比——索引直接作废。
实操建议:
- 统一声明为
@start_date datetime2(3)(毫秒级足够,节省空间)或@end_date datetime2(7)(与业务表字段对齐) - 若接收字符串(如
@date_str NVARCHAR(20)),开头就显式转换:SET @start_date = TRY_CONVERT(datetime2(3), @date_str);失败则RETURN,不依赖隐式行为 - 调用方传
GETDATE()或应用层生成的精确时间戳时,datetime2不会截断溢出
WHERE 条件里别用函数包裹日期列
写成 WHERE CONVERT(date, order_time) = '2024-06-15' 看着方便,但会让 SQL Server 放弃 order_time 上的索引,降级为全表扫描——哪怕只有几万行,延迟也会明显升高。
正确做法是保持列裸露,用范围代替等值:
- 查某一天全部数据:
order_time >= '2024-06-15' AND order_time (注意是 <code>,不是 <code>) - 查最近 7 天:
order_time >= DATEADD(day, -7, GETDATE()),别用BETWEEN——它闭区间特性在含毫秒时容易漏掉23:59:59.9999999 - 若必须按日期聚合,先在 CTE 中缩小数据集:
WITH filtered AS (SELECT * FROM orders WHERE order_time >= @s AND order_time
复合索引的日期字段必须放最左位
假设常查“某用户在某时间段内的订单”,条件是 WHERE user_id = @uid AND order_time BETWEEN @s AND @e,但索引建成了 IX_user_time ON orders (user_id, order_time)——这等于白建。SQL Server 只能用 user_id 做等值查找,再逐行检查 order_time,无法跳过无关用户的数据。
必须把高频筛选的日期字段前置:
- 推荐建法:
CREATE INDEX IX_order_time_user ON orders (order_time, user_id) - 如果还带状态过滤(如
status IN ('paid', 'shipped')),可扩展为(order_time, status, user_id) - 别把低选择性字段(如
status)放最前
动态时间计算要预先固化边界值
像“上月”“上周”这类周期,别在 WHERE 里实时算:WHERE order_time >= DATEADD(MONTH, -1, DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1)) 这种写法每次执行都重新计算,且难读难维护。
实操建议:
- 在存储过程开头就固化:
DECLARE @month_start datetime2(3) = DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1); DECLARE @last_month_end datetime2(3) = DATEADD(ms, -3, @month_start); - 再用这些变量做查询:
WHERE order_time >= @last_month_start AND order_time - 避免在
WHERE中嵌套多层DATEADD/DATEDIFF,否则执行计划易退化为扫描
真正麻烦的是时间精度和索引列类型的一致性——差一个类型、差一个括号、差一个毫秒,就可能让百万行查询从 20ms 慢到 3s。











