执行计划的查询结果不正确,不确定问题的确切原因

Dan*_*rts 1 sql-server sql-server-2012

昨天我们有一个查询,旨在翻阅结果,但在结果的第一页上,查询只返回 4 条记录,而不是预期的 25 条。以任何方式更改查询都会导致 25 条记录,这对我来说意味着这是一次执行计划问题。我不熟悉在生产中查看执行计划的方法,并且在 sql studio 中运行查询没有导致相同的问题,可能是由于细微的差异。

我阅读了一些建议,认为这种事情可能是由损坏的索引引起的。我在数据库上运行了 checkdb 并没有发现错误。最后我清除了执行计划,一切都很好。

如果它不是某种类型的损坏问题,而只是非常具体的执行计划的问题,那么这是否意味着执行计划中存在错误并且我们在 SQL Server 中遇到了错误?我们在 SQL Server 2012 RTM 上没有更新,所以我查看了累积更新和服务包中所有修复的文档,但没有一个问题似乎与我们自己的相关。

关于可能导致这种情况的任何其他想法或想法?

(@P1 varchar(8000),@P2 bit,@P3 varchar(8000),@P4 bit,@P5 varchar(8000))

SELECT e.*
FROM (

    SELECT 

        TOP 25
        ROW_NUMBER() OVER (
            ORDER BY AddedDate DESC
        ) AS Row,

        ID
        , Prefix
        , FirstName
        , LastName
        , Company
        , Address
        , City
        , State
        , Zip
        , Country
        , WorkPhone
        , HomePhone
        , MobilePhone
        , Email
        , MailingLists
        , AddedDate
        , AwaitingOptin
        , OptInDate
        , Processed
        , ProcessedDate
        , Deleted
        , Source
        , GRRecID
        , DatabaseID
        , (SELECT COUNT(*) 
            FROM TableA
            LEFT OUTER JOIN TableB ON TableA.GRRecID = TableB.GRRecID
            WHERE TableA.AccountID = @P1 
                AND TableA.Email = TableC.Email
                AND TableA.Deleted = @P2 
                ) AS Matches
    FROM TableC
    WHERE AccountID = @P3  
        AND Processed = @P4  
        AND Deleted = 0
        AND Source = @P5  
) AS e
WHERE Row >= 1 AND Row <= 25
ORDER BY Row
Run Code Online (Sandbox Code Playgroud)

Mik*_*son 10

我不相信结果是不正确的,您的查询不明确。从语义上讲,您的子查询枚举所有行,然后您选择前 25 行而不指定 order by。这意味着 SQL Server 可以自由地返回它感觉的任何行,并且这些行不必是枚举 1 到 25 的行。它可以是任何随机行,在您的情况下,在范围内枚举了四行1 到 25。其余的在主查询中被过滤掉了。

您可以通过删除子查询中的 top 语句来修复查询。

根据您的查询,您认为最好的行动方案是什么?

我会测试您拥有的所有选项,然后选择性能最佳的选项。

我会给你一个更多的测试选项,假设ID是主键列,TableCAddedDate不是聚集键。

添加一个索引,AddedDate包括在枚举发生之前您需要过滤掉行的列。

create index IX_TableC_AddedDate on TableC(AddedDate) 
  include(AccountID, Processed, Deleted, Source)
Run Code Online (Sandbox Code Playgroud)

重写您的子查询,以便唯一返回IDrow然后加入TableC主查询以获取其余列。

SELECT E.Row,
       C.*, -- don't use * here, include the columns you actually need
       (
       SELECT COUNT(*) 
       FROM TableA as A
         LEFT OUTER JOIN TableB AS B 
            ON A.GRRecID = B.GRRecID
       WHERE A.AccountID = @P1 AND 
             A.Email = C.Email AND 
             A.Deleted = @P2 
       ) AS Matches
FROM (
     SELECT ROW_NUMBER() OVER (ORDER BY C.AddedDate DESC) AS Row,
            C.ID
     FROM TableC as C
     WHERE AccountID = @P3 AND 
           Processed = @P4 AND 
           Deleted = 0 AND 
           Source = @P5  
     ) AS E
  INNER JOIN TableC as C -- Join back to TableC
    ON E.ID = C.ID
WHERE E.Row >= 1 AND E.Row <= 25
ORDER BY E.Row
Run Code Online (Sandbox Code Playgroud)

您可以尝试使用更有效的索引,但参数的数据类型有些令人困惑。是AccountID一个真正的VARCHAR(8000)?如果不是,您应该更改数据类型以匹配列的数据类型,在这种情况下,更好的索引使用将导致 with AccountID

create index IX_TableC_AccountID_AddedDate on TableC(AccountID, AddedDate) 
  include(Processed, Deleted, Source)
Run Code Online (Sandbox Code Playgroud)

这同样Source取决于它是什么数据类型。如果数据类型合适,您也可以AddedDate在索引的列列表之前添加该数据类型。

如果想避免任何残留谓词,您甚至可以使用索引中 where 子句中的所有列。

create index IX_TableC_ on TableC(AccountID, Source, Processed, Deleted, AddedDate)
Run Code Online (Sandbox Code Playgroud)

只要确保AddedDate是索引中的最后一列,并且AddedDate在 where 子句中没有未使用的列之前没有任何列。

Itzik Ben-Gan 发表的一篇关于相同查找问题的博客文章(用于偏移获取):

ROW_NUMBER用于分页时避免不必要的查找