【问题标题】:Can this ORDER BY on a CASE clause be made faster?可以使 CASE 子句上的 ORDER BY 更快吗?
【发布时间】:2012-09-20 22:35:17
【问题描述】:

我正在从一个包含约 3.5 亿条记录的表中选择结果,它的运行速度非常慢 - 大约 10 分钟。罪魁祸首似乎是 ORDER BY,好像我将其删除,查询只需要一点时间。这是要点:

SELECT TOP 100
    (columns snipped)
FROM (
    SELECT 
        CASE WHEN (e2.ID IS NULL) THEN
            CAST(0 AS BIT) ELSE CAST(1 AS BIT) END AS RecordExists,
        (columns snipped)
    FROM dbo.Files AS e1
    LEFT OUTER JOIN dbo.Records AS e2 ON e1.FID = e2.FID
)  AS p1
ORDER BY p1.RecordExists

基本上,我是按照Files 是否有对应的Record 来排序结果,因为那些不需要先处理。我可以使用 WHERE 子句运行两个查询,但如果可能的话,我宁愿在一个查询中进行。

有什么办法可以加快速度吗?

【问题讨论】:

  • 当您删除ORDER BY 时,您是否也删除了TOP 100

标签: sql performance sql-server-2008 sql-order-by


【解决方案1】:

最终的问题是,在子查询中使用 CASE 会引入 ORDER BY,而不是以 sargable 方式使用的东西。因此,必须首先对整个中间结果集进行排序以找到 TOP 100 - 这是全部 350+ 百万条记录!2

在这种特殊情况下,将 CASE 移到外部 SELECT 并使用 DESC 排序(首先在当前 RecordExists 中放置 NULL 值,这意味着“0”)应该可以解决问题1 .这不是一个通用的方法,虽然.. 但排序应该快得多 iff Files.ID 被索引。 (如果查询仍然很慢,请查阅查询计划以了解 ORDER BY 为何使用索引。)

另一种选择可能是为 RecordExists(也被索引)包含一个持久计算列,该列可用作 ORDER BY 中的索引。

再一次,这个想法是 ORDER BY 可以处理 sargable 的内容,它只需要在索引内按顺序读取(直到所需的记录数以匹配外部限制)和不是即时订购 350+ 百万条记录 :)

SQL Server 然后能够将此排序(和限制)向下 推送到子查询中,而不是等待子查询的中间结果集出现 。根据订购的什么查看查询计划差异。


1 示例:

SELECT TOP 100
    -- If needed
    CASE WHEN (p1.ID IS NULL) THEN
        CAST(0 AS BIT) ELSE CAST(1 AS BIT) END AS RecordExists,
    (columns snipped)
FROM (
    SELECT 
        (columns snipped)
    FROM dbo.Files AS e1
    LEFT OUTER JOIN dbo.Records AS e2 ON e1.FID = e2.FID
)  AS p1
-- Hopefully ID is indexed, DESC makes NULLs (!RecordExists) go first
ORDER BY p1.ID DESC

2 实际上,它似乎可以假设在前 100 个 0 之后停止而没有全排序 .. 至少在一些极端的查询计划优化下 在一个封闭的函数范围内,但这取决于什么时候在中间结果集中遇到0(在最初的几千或直到数亿或永远不会?)。我非常怀疑无论如何,SQL Server 是否会导致这种极端情况。也就是说,不要指望这种仍然不可分割的行为。

【讨论】:

  • 这是一个很好的答案。我的问题的其余部分是我正在使用 LINQ 来构建这些查询,并且我无法弄清楚如何在 LINQ 中提出您的方法,而 Richard 的方法很简单。不过很有教育意义 - 谢谢。
【解决方案2】:

试试这个表格

SELECT TOP(100) *
FROM (
    SELECT TOP(100)
        0 AS RecordExists
        --,(columns snipped)
    FROM dbo.Files AS e1
    WHERE NOT EXISTS (SELECT * FROM dbo.Records e2 WHERE e1.FID = e2.FID)
    ORDER BY SecondaryOrderColumn
) X
UNION ALL
SELECT * FROM (
    SELECT TOP(100)
        1 AS RecordExists
        --,(columns snipped)
    FROM dbo.Files AS e1
    INNER JOIN dbo.Records AS e2 ON e1.FID = e2.FID
    ORDER BY SecondaryOrderColumn
) X
ORDER BY SecondaryOrderColumn

主要指标:
记录 (FID)
文件(FID、SecondaryOrdercolumn)

【讨论】:

    【解决方案3】:

    它慢得多的原因是因为它实际上是一个非常不同的查询,没有 order by 子句。

    使用 order by 子句: 从全部 3.5 亿行中找出所有匹配的记录。然后对它们进行排序。

    没有 order by 子句: 查找前 100 条匹配记录。停下来。

    【讨论】:

    • 没这么简单。我相信一旦引入 CASE,使用索引的能力就会被抛弃.. 因为如果它可以在索引上使用 ORDER BY,那么它会很快并且可以停止前 100 个(从索引端依次读取)。
    • 可能,如果您假设瓶颈是排序。但也有可能瓶颈是必须读取 3.5 亿行。我认为@MartinSmith 在问题 cmets 中提出了正确的问题。
    • @pst:使用 any 计算值(例如 case 表达式)禁止优化器使用涉及此类表达式基值的索引。
    • @NicholasCarey .. 但这个答案并没有解决 ORDER BY over an index 的情况。
    【解决方案4】:

    问:如果你说唯一的区别是“with/outout”和“order by”,那么你能以某种方式将“top 100”移到inner选择中吗?

    示例:

    SELECT
        (columns snipped)
    FROM (
        SELECT TOP 100
            CASE WHEN (e2.ID IS NULL) THEN
                CAST(0 AS BIT) ELSE CAST(1 AS BIT) END AS RecordExists,
            (columns snipped)
        FROM dbo.Files AS e1
        LEFT OUTER JOIN dbo.Records AS e2 ON e1.FID = e2.FID
    )  AS p1
    ORDER BY p1.RecordExists
    

    【讨论】:

    • 在订购前选择前 100 个会改变查询的含义。在进入 1 之前,我想要尽可能多的 RecordExists = 0(最多 100 个)。
    【解决方案5】:

    在 SQL Server 中,null 值的排序低于域中的任何值。鉴于这两个表:

    create table dbo.foo
    (
      id   int         not null identity(1,1) primary key clustered ,
      name varchar(32) not null unique nonclustered ,
    )
    
    insert dbo.foo ( name ) values ( 'alpha' )
    insert dbo.foo ( name ) values ( 'bravo' )
    insert dbo.foo ( name ) values ( 'charlie' )
    insert dbo.foo ( name ) values ( 'delta' )
    insert dbo.foo ( name ) values ( 'echo' )
    insert dbo.foo ( name ) values ( 'foxtrot' )
    go
    
    create table dbo.bar
    (
      id     int         not null identity(1,1) primary key clustered ,
      foo_id int             null foreign key references dbo.foo(id) ,
      name   varchar(32) not null unique nonclustered ,
    )
    go
    insert dbo.bar( foo_id , name ) values( 1 , 'golf' )
    insert dbo.bar( foo_id , name ) values( 5 , 'hotel' )
    insert dbo.bar( foo_id , name ) values( 3 , 'india' )
    insert dbo.bar( foo_id , name ) values( 5 , 'juliet' )
    insert dbo.bar( foo_id , name ) values( 6 , 'kilo' )
    go
    

    查询

    select *
    from      dbo.foo foo
    left join dbo.bar bar on bar.foo_id = foo.id
    order by bar.foo_id, foo.id
    

    产生以下结果集:

    id name    id   foo_id name
    -- ------- ---- ------ -------
    2  bravo   NULL NULL   NULL
    4  delta   NULL NULL   NULL
    1  alpha   1    1      golf
    3  charlie 3    3      india
    5  echo    2    5      hotel
    5  echo    4    5      juliet
    6  foxtrot 5    6      kilo
    
    (7 row(s) affected)
    

    应该允许查询优化器使用合适的索引(如果存在的话);但是,它保证会使用任何此类索引。

    【讨论】:

      【解决方案6】:

      你可以试试这个吗?

      SELECT TOP 100
          (columns snipped)
      FROM dbo.Files AS e1
      LEFT OUTER JOIN dbo.Records AS e2 ON e1.FID = e2.FID
      ORDER BY e2.ID ASC
      

      这应该首先告诉您 e2.ID 为 null 的位置。此外,请确保 Records.ID 已编入索引。这应该会给你你想要的顺序。

      【讨论】:

        猜你喜欢
        • 2017-04-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-12-12
        • 1970-01-01
        • 2021-07-20
        • 2021-12-13
        相关资源
        最近更新 更多