Maui Blazor 中文社区 QQ群:645660665

一些SQL标准优化方案,这是 SQL Server 必考级、实战级 的东西

SQL Server BETWEEN 日期范围优化(实战规范版)

适用场景:Orders / Products / 报表 / 对账
级别:SQL Server 必考级 · 生产级 · 架构规范级


一、为什么 BETWEEN 日期 AND 日期 容易慢、还容易错

❌ 常见写法(问题很多)

WHERE OrderDate BETWEEN '2026-01-09'
                    AND '2026-01-09 23:59:59.000'

问题 1️⃣:精度陷阱

  • datetime / datetime2 具有 毫秒 / 100ns 精度

  • 实际数据可能是:

    • 23:59:59.997
    • 23:59:59.9999999

👉 会被 BETWEEN 漏掉


问题 2️⃣:参数化不友好

  • 执行计划难以复用
  • ORM / 存储过程容易抖动(Plan Cache Pollution)

问题 3️⃣:索引利用率下降

  • BETWEEN闭区间
  • 优化器有时会扩大扫描范围
  • Index Seek 不稳定

二、SQL Server 官方推荐:唯一正确的日期写法

✅ 半开区间(SARGABLE)

WHERE OrderDate >= @DateStart
  AND OrderDate <  DATEADD(DAY, 1, @DateStart)

如果你有结束时间:

WHERE OrderDate >= @DateStart
  AND OrderDate <  @DateEnd

🔑 核心原则

左闭右开区间 [start, end)
永远不要用 BETWEEN 做日期范围


三、你这个场景的“标准模板”

📌 查询某一天(最常用)

DECLARE @Date date = '2026-01-09';

WHERE OrderDate >= @Date
  AND OrderDate <  DATEADD(DAY, 1, @Date)

优点

  • ✔ 不漏数据
  • ✔ 可走索引
  • ✔ 执行计划稳定

📌 查询日期范围(报表 / 对账)

WHERE OrderDate >= @DateFrom
  AND OrderDate <  DATEADD(DAY, 1, @DateTo)

不要用 BETWEEN


四、索引设计(性能真正起飞的关键)

✅ 正确的索引顺序(非常重要)

CREATE NONCLUSTERED INDEX IX_Orders_OrderDate_Status
ON dbo.Orders (OrderDate, Status)
INCLUDE (OrderID, CustomerID, SubTotal, Receivd);

为什么 OrderDate 必须放第一?

  • 范围查询(>= <)必须在 索引最左
  • Status 是等值过滤,放第二
  • INCLUDE 列避免回表

五、千万别这么写(全部是坑)

❌ 1️⃣ 对列做函数

WHERE CONVERT(date, OrderDate) = '2026-01-09'

👉 索引直接失效


❌ 2️⃣ BETWEEN + 减一秒

BETWEEN '2026-01-09'
    AND DATEADD(second, -1, '2026-01-10')
  • 老写法
  • 不精确
  • 不稳定

❌ 3️⃣ 字符串比较

WHERE OrderDate BETWEEN '2026-01-09' AND '2026-01-10'
  • 隐式转换
  • 执行计划不可控

六、不能改 SQL?(只能加索引的补救方案)

⚠️ 这是兜底,不是最佳实践

CREATE NONCLUSTERED INDEX IX_Orders_OrderDateOnly
ON dbo.Orders (OrderDate);

你仍然会遇到:

  • 扫描行数多
  • Sort
  • 性能抖动

七、执行计划对比(本质差异)

写法 Index Seek Sort
BETWEEN ⚠️ 不稳定 常见
>= AND < ✅ 稳定
CONVERT(date, OrderDate)

八、一句话总结(团队规范)

SQL Server 中:
❌ 禁用 BETWEEN 日期 AND 日期
✅ 统一使用:

col >= start AND col < end

九、使用日期字符串比较的正确姿势

❌ 当前写法(有索引问题)

WHERE a.OrderDate = CONVERT(char(10), GETDATE(), 120)
  AND a.Status = N'已结账'

问题:

  • 字符串比较
  • 隐式转换
  • 无法 Index Seek

✅ 正确写法(可走索引)

WHERE a.OrderDate >= CONVERT(date, GETDATE())
  AND a.OrderDate <  DATEADD(day, 1, CONVERT(date, GETDATE()))
  AND a.Status = N'已结账'

十、聚合查询的推荐索引(SUM 场景)

CREATE NONCLUSTERED INDEX IX_Orders_OrderDate_Status
ON dbo.Orders (OrderDate, Status)
INCLUDE (SubTotal, VISA);

👉 执行计划会变成:

  • Index Seek
  • Stream Aggregate
  • 无回表

十一、完整优化后的聚合 SQL

SELECT
    SUM(a.SubTotal)                     AS TotalSubTotal,
    SUM(ISNULL(a.VISA, 0))              AS TotalVISA,
    SUM(a.SubTotal - ISNULL(a.VISA, 0)) AS TotalOther
FROM dbo.Orders a
WHERE a.OrderDate >= CONVERT(date, GETDATE())
  AND a.OrderDate <  DATEADD(day, 1, CONVERT(date, GETDATE()))
  AND a.Status = N'已结账';

十二、搜索备注字段(LIKE 前缀优化)

WHERE Status = N'XXX'
  AND Remark LIKE N'新网络订单%'

常见问题

  • NVARCHAR(MAX) / TEXT 不能做索引键
  • 计算列派生自 MAX 仍不可索引

✅ 标准改造方案(生产级)

1️⃣ 新增可索引列

ALTER TABLE dbo.Orders
ADD RemarkIndex NVARCHAR(50);

2️⃣ 初始化历史数据

UPDATE dbo.Orders
SET RemarkIndex = LEFT(Remark, 50);

3️⃣ 建索引

CREATE NONCLUSTERED INDEX IX_Orders_Status_RemarkIndex
ON dbo.Orders (Status, RemarkIndex);

4️⃣ 同步机制(应用层或触发器)

CREATE TRIGGER trg_Orders_RemarkIndex
ON dbo.Orders
AFTER INSERT, UPDATE
AS
BEGIN
    UPDATE o
    SET RemarkIndex = LEFT(o.Remark, 50)
    FROM dbo.Orders o
    JOIN inserted i ON o.OrderID = i.OrderID;
END;

十三、WHERE LEN(a.VIPID) > 0 的索引优化

❌ 当前问题

WHERE LEN(a.VIPID) > 0
  • 函数包列
  • 逐行计算
  • 索引失效

✅ 等价且可走索引的写法

WHERE a.VIPID IS NOT NULL
  AND a.VIPID <> N''

🚀 过滤索引(VIPID 大多为空时)

CREATE NONCLUSTERED INDEX IX_Customers_VIPID_NotEmpty
ON dbo.Customers (VIPID)
INCLUDE (
    CustomerID,
    ConsumerOnOrder,
    ConsumerOnPurchase,
    Discount
)
WHERE VIPID IS NOT NULL
  AND VIPID <> N'';

十四、最终结论(可贴墙)

BETWEEN + 日期
LEN(column)
❌ 字符串比较日期

>= AND <
✅ 合理索引顺序
✅ 覆盖索引 / 过滤索引

这就是 SQL Server 高性能查询的底层逻辑


如果你愿意,我可以帮你 再整理一版「公司级 SQL 性能规范 PDF / Markdown 模板」,直接给新人用。

posted @ 2026-01-09 19:50  AlexChow  阅读(6)  评论(0)    收藏  举报