是否有选项或提示可以提高“in”子句中具有多个值的查询的性能

Mic*_*elD 6 performance sql-server execution-plan execution

我们有一个表 CustomerNote,有 4 列 ID、CustomerID、Note、Date

CustomerID asc, Date desc 上有一个索引

当执行以下查询时

select top 30 
    Date 
from CustomerNote
where CustomerID in (1,5)
order by Date desc
Run Code Online (Sandbox Code Playgroud)

使用了索引,但它仍然获取 customerID 1 和 5 的所有 CustomerNote,然后排序/置顶,导致大量 CPU 使用。 在此输入图像描述

这是由于“in”子句中的多个值造成的。我知道“in”子句的值永远不会超过 10 个,因此如果 sql server 迭代 10 个,为每个 customerID 获取至少 30 个值并进行合并、排序和顶部,这将是一个更好的方法。是否有查询提示或选项可以实现此目的?

Pau*_*ite 9

您可能最好手动编写所需的转换,但本着寻找优化器可以通过最少的更改为您做的事情的精神:

SELECT TOP (30) 
    CN.[Date] 
FROM dbo.CustomerNote AS CN
WHERE 
    CN.CustomerID IN (SELECT 1 UNION SELECT 5)
ORDER BY 
    CN.[Date] DESC;
Run Code Online (Sandbox Code Playgroud)

计划

是的,如果优化器能够在不改变语法的情况下为您探索这种选项,那就太好了。

示例表和数据

CREATE TABLE dbo.CustomerNote
(
    CustomerID integer NOT NULL,
    [Date] datetime NOT NULL
);
Run Code Online (Sandbox Code Playgroud)
WITH 
    N (n) AS
    (
        SELECT 
            SV.number 
        FROM master.dbo.spt_values AS SV
        WHERE
            SV.[type] = N'P'
            AND SV.number >= 1
    )
INSERT dbo.CustomerNote
    WITH (TABLOCKX)
(
    CustomerID, 
    [Date]
)
SELECT 
    C.n, 
    DATEADD
    (
        MINUTE, 
        -D.n * RAND(CHECKSUM(NEWID())) * 1000,
        GETDATE()
    )
FROM N AS C
CROSS JOIN N AS D
WHERE
    C.n BETWEEN 1 AND 9;

CREATE NONCLUSTERED INDEX 
    [IX dbo.CustomerNote CustomerID, Date-] 
ON dbo.CustomerNote
    (CustomerID, [Date] DESC);
Run Code Online (Sandbox Code Playgroud)

这会为 10 个不同的客户加载 2047 个随机日期值:

SELECT CN.CustomerID, NumRows = COUNT_BIG(*) 
FROM dbo.CustomerNote AS CN
GROUP BY CN.CustomerID
ORDER BY CN.CustomerID;
Run Code Online (Sandbox Code Playgroud)
客户ID 行数
1 2047
2 2047
3 2047
4 2047
5 2047
6 2047
7 2047
8 2047
9 2047

运行解决方案代码会生成一个执行后计划,其中从客户 1 的有序索引查找中读取14 行,从客户 5 的类似查找中读取17 行:

实际计划

db<>小提琴演示

该计划不会读取每个客户的所有 2047 行。

另请注意,此解决方案不需要排序运算符。


尽管合并串联需要满足一些排序条件,但该解决方案非常通用。

如果您想投影不同的列,则该列需要成为索引键的一部分(而不是作为包含)以满足排序要求;或者您可以仅获取表的一个键,并在找到所需的少量键后作为单独的步骤查找额外的列。

带有额外专栏的其他演示:


Cha*_*ace 7

VALUES如果您加入表值参数、临时表或子句,似乎可能会更好。这意味着每个CustomerID将在相关中单独查询APPLY然后排序。

select top (30) 
  cn.Date 
from (values
    (1),
    (5)
) v(CustomerID)
cross apply (
    select top (30) cn.*
    from CustomerNote cn
    where v.CustomerID = cn.CustomerID
    order by
      cn.Date desc
) cn
order by
  cn.Date desc;
Run Code Online (Sandbox Code Playgroud)