Dodgy T-SQL 查询执行让我发疯

ZCT*_*ZCT 11 sql-server t-sql sql-server-2019

执行计划SQL Server 2019。这是 xml 计划要点的链接。

您好,我花了很长时间才明白为什么当它找不到包含其中一种状态的记录时,为什么这个查询需要 0.02 秒才能执行。当它找到具有包含状态之一的记录时,它往往要快得多。我猜这是因为一旦找到匹配的 1 行,查询就会停止。

SELECT TOP 1 IDNum,
             FORMAT(Date, 'M/d/yy') AS theDate,
             Status,
             Rate
FROM   theDB
       INNER JOIN DomainTable
               ON theDB.IDNum = DomainTable.IDNum
WHERE  DomainIP = '127.0.0.1'
       AND status IN ( 'Active', 'To ReActivate', 'To Deactivate', 'Deactivate ASAP',
                       'SUSPENDED', 'SUSPENDED X', 'SUSPENDED Y', 'SUSPENDED Z' )
ORDER  BY theDB.IDNum DESC 
Run Code Online (Sandbox Code Playgroud)

(DomainIP属于DomainTable,其他属于DB)

在执行计划中,最大的成本是 TOP N SORT,占 33% 聚集索引搜索占 29% 对 DomainTable 的键查找使用了 29% 在 DomainTable 上搜索 IP 的索引是 9%

我的问题是:

有没有办法让TOP N不那么重?

0.02 秒并不是那么慢,但这个查询也很轻。所以我想尽可能地优化它。

DomainTable 中只有四个条目,其中 IP 为 127.0.0.1,因此基本上需要 0.2 秒来决定返回哪个条目,结果结果是没有一个,因为其中任何一个的状态都不正确. 有没有办法只创建一个将所有这些信息保存在 RAM 或其他东西中的索引?

Jos*_*ell 15

有没有办法让TOP N不那么重?

我认为这里对执行计划的工作方式存在一些误解。

该数字只是估计成本,这是 SQL Server 用来确定最有效执行计划的模型。它不会在运行时更新,因此即使操作员使用的资源很少,估计成本仍会显示为创建计划时的状态。

查看您提供的运行时计划,Sentry One Plan Explorer 可以更轻松地看到其中一些“昂贵”的操作符根本没有运行(它们是“灰色的”):

在此处输入图片说明

我花了很长时间才明白为什么这个查询需要 0.02 秒来执行

我不确定您是如何测量这里的时间的,但执行计划表明整个查询的运行时间不到 1 毫秒,编译时间为 9 毫秒。请参阅计划 XML 的摘录:

<QueryPlan DegreeOfParallelism="1" MemoryGrant="1024" CachedPlanSize="48" CompileTime="9" CompileCPU="9" CompileMemory="736">
...
<QueryTimeStats CpuTime="0" ElapsedTime="0" />
Run Code Online (Sandbox Code Playgroud)

Hannah 对查询的一般改进提出了一些很好的建议,一定要检查一下。但我认为也值得指出这一点。

您可能可以使用索引视图消除 TopN Sort ,但我不确定此时是否值得开销。


Han*_*non 10

尝试创建以下索引(我使用的是我认为类似于索引名称命名约定的内容):

CREATE INDEX DomainTable$DomainIP$IDNum
ON dbo.DomainTable (DomainIP, IDNum DESC);

CREATE INDEX theDB$idx001
ON dbo.theDB (IDNum DESC)
INCLUDE (theDate, [Status], Rate);

Run Code Online (Sandbox Code Playgroud)

第一个索引允许 SQL Server 在 DomainIP 上进行查找,这将非常快,并且它包括 IDNum 列,按降序排列,这将消除查询计划中的键查找操作,并消除所需的降序排序。

第二个索引也可能允许 SQL Server 消除排序 - 看看它是否有助于或阻碍查询执行。如果您为您的问题添加一个最小、完整且可验证的示例,我将能够为您测试。

无论如何,请考虑对您的查询进行以下修改:

IF OBJECT_ID(N'tempdb..#statii', N'U') IS NOT NULL
BEGIN
    DROP TABLE #statii;
END
CREATE TABLE #statii 
(
    [status] varchar(20) NOT NULL
        PRIMARY KEY CLUSTERED
);

INSERT INTO #statii ([status])
VALUES ('Active')
    , ('To ReActivate')
    , ('To Deactivate')
    , ('Deactivate ASAP')
    , ('SUSPENDED')
    , ('SUSPENDED X')
    , ('SUSPENDED Y')
    , ('SUSPENDED Z');

SELECT TOP 1 theDB.IDNum
    , CONVERT(varchar(30), theDB.theDate, 22)
    , theDB.[Status]
    , theDB.Rate 
FROM theDB 
    INNER JOIN DomainTable ON theDB.IDNum = DomainTable.IDNum 
    INNER JOIN #statii s ON theDB.[status] = s.[status]
WHERE DomainTable.DomainIP = '127.0.0.1' 
ORDER BY theDB.IDNum DESC;
Run Code Online (Sandbox Code Playgroud)

有几点需要注意:

  1. 我没有使用该WHERE ... IN (...)子句,而是将这些值插入到临时表中并加入它们。您可能不会注意到这种方法对性能的巨大提升,但是随着IN (...)子句中的项目列表越来越大,性能差异变得越来越明显。IN (...)如果结果会妨碍性能,您可能希望将其恢复为子句。我认为值得测试。

  2. 不要使用FORMAT- 它很慢。使用CONVERTCAST在可能的情况下。

  3. 使用表别名,就像我为#statii表所做的那样。

  4. 关键字大写,数据类型保持小写。

  5. 永远不要 1使用关键字作为列名。我看着你,status。该列应该被命名为BlahStatus其中Blah是表的名称,还是那种状态,或东西


1 - 好吧,也许永远不会有点苛刻。几乎从不。