WHERE 分支的优化

Jes*_*ams 3 sql-server query-performance

我有一个针对可以增长到数百万行的表执行的查询。查询来自我们使用的 QA 工具,它超出了数据库的标准功能(就索引的内容以及索引方式和原因而言)。查询是:

SELECT id FROM thisTable t
WHERE col = 'val'
AND ((not exists (SELECT 1 FROM thisTable WHERE refid = t.id) and refbool = 0) or refbool = 1)
ORDER BY newid()
Run Code Online (Sandbox Code Playgroud)

基本上,假设表中有idrefidrefbool,和col列。所以你可以有如下数据:

  id  |  refid  |  refbool  |  col
------------------------------------
   1  |   NULL  |    0      |  val
   2  |   NULL  |    0      |  val
   3  |   NULL  |    0      |  val
   4  |    2    |    1      |  val
   5  |   NULL  |    0      |  val
   6  |    1    |    1      |  val
Run Code Online (Sandbox Code Playgroud)

查询永远不应该为 (1, 2) 中的 id 选择行,因为它们被其他行引用。它应该只抓取行,其中refbool = 1, OR refbool = 0AND 该行的 id 不是任何其他行的refid。这个语句非常糟糕,但我不确定更好的查询会是什么样的。假设不能添加索引、视图、存储过程或其他底层机制——它必须是一个查询。

整个查询要大得多,JOINS有两个额外的表,并收集了相当多的数据。但是,我已将它缩小到这一特定位,因为注释掉这一行会将查询执行时间从 16 秒缩短到 <1 秒。

我还对行重新排序,newid()因为我需要随机选择一个示例项目。ORDER BY即使保留第三行,删除也使查询速度显着加快。这两个操作的结合似乎导致了缓慢。我曾尝试设计 CTE,但未能提高性能。

我看过执行计划。将添加可以改进此查询的索引。但是,内部 QA 工具的性能并不优先于客户端生产环境中的性能,并且在 QA 环境中对与索引相关的实用程序的结构进行更改等。使其作为 QA 环境的有用性无效,因为它可能会执行与生产环境不同。

通过更改查询本身的逻辑,我当然可以编写一个比我当前的查询性能更差的查询。我相信我们都可以。我要求应用这种推理来提高查询的性能。

Pau*_*ite 5

执行计划未包括在内,但此类查询的典型问题(排序除外)是优化器选择嵌套循环反半连接而没有良好的支持索引。它也可能是一个流氓 top (1),或者向带有嵌套启动过滤器和反半连接的半连接的性能不佳的转换。

无论如何,有两种常用的解决方法:

  1. OR手动重写为 a UNION(或者,如果保证不相交,则重写为 a UNION ALL)。
  2. 将 重写NOT EXISTS为左连接,过滤保留的一侧为NULL

以下内容包含两者:

DECLARE @thisTable table
(
    id integer PRIMARY KEY,
    refid integer NULL,
    refbool bit NOT NULL,
    col varchar(10) NOT NULL
);

INSERT @thisTable
    (id, refid, refbool, col)
VALUES
    (1, NULL, 0, 'val'),
    (2, NULL, 0, 'val'),
    (3, NULL, 0, 'val'),
    (4,  2  , 1, 'val'),
    (5, NULL, 0, 'val'),
    (6,  1  , 1, 'val');
Run Code Online (Sandbox Code Playgroud)
SELECT
    U.id
FROM 
(
    -- T.refbool = 1
    SELECT T.id 
    FROM @thisTable AS T
    WHERE 
        T.col = 'val'
        AND T.refbool = 1

    -- Or (disjoint)
    UNION ALL

    -- T.refbool = 0 and not exists
    SELECT T.id 
    FROM @thisTable AS T
    LEFT JOIN @thisTable AS T2
        ON T2.refid = T.id
    WHERE 
        T.col = 'val'
        AND T.refbool = 0
        AND T2.id IS NULL
) AS U
ORDER BY 
    CHECKSUM(NEWID());
Run Code Online (Sandbox Code Playgroud)

db<>fiddle 在线演示

有关随机排序的更多选择,请参阅现有的问答:

不要只尝试最佳答案。