添加ORDER BY语句时SQL查询速度很慢

pme*_*yer 3 sql-server performance

我有一个[Documents]包含以下列的表:

Name (string)
Status (string)
DateCreated [datetime]
Run Code Online (Sandbox Code Playgroud)

该表有大约100万条记录.这三列中的所有三列都有一个索引(每个列都有一个索引).

当我运行此查询时:

select top 50 *  
from [Documents] 
where (Name = 'None' OR Name is null OR Name = '') 
  and Status = 'New';
Run Code Online (Sandbox Code Playgroud)

执行速度非常快(300毫秒)

如果我运行相同的查询,但使用该ORDER BY子句,它真的很慢(3000毫秒)

select top 50 *  
from [Documents] 
where (Name = 'None' OR Name is null OR Name = '') 
  and Status = 'New'
order by DateCreated;
Run Code Online (Sandbox Code Playgroud)

我理解它在另一个索引(DateCreated)中搜索,但它应该真的慢得多吗?如果是这样,为什么?我能做些什么来加速这个查询(复合索引)?

谢谢

顺便说一句:所有索引都包含DateCreated了非常低的碎片,实际上我进行了重组并且它没有改变任何东西.

spe*_*593 6

至于查询速度较慢的原因,查询需要"按顺序"返回行,因此需要进行排序,或者需要使用索引.

使用带有CreatedDate前导列的索引,SQL Server可以避免排序.但SQL Server还必须访问基础表中的页面以评估是否要返回该行,查看Status和Name列中的值.

如果优化器选择不使用CreatedDate作为前导列的索引,则需要首先找到满足谓词的所有行,然后执行排序操作以按顺序获取这些行.然后它可以从排序集返回前五十行.(SQL Server不一定需要对整个集合进行排序,但它需要遍历整个集合,并进行足够的排序以确保它具有需要返回的"前50个".

注意:我怀疑你已经知道这一点,但要澄清一下:SQL Server尊重ORDER BY之前的TOP 50.如果您希望任何50行满足谓词,但不一定是具有最低值DateCreated的50行,您可以重构/重写查询,获取(最多)50行,然后执行那些排序.


一些提高性能的想法

添加复合索引(如其他答案所示)可能会提供一些改进,例如:

 ON Documents (Status, DateCreated, Name)
Run Code Online (Sandbox Code Playgroud)

SQL Server可能能够使用该索引来满足Status上的等式谓词,并且还可以在没有排序操作的情况下以DateCreated顺序返回行.SQL服务器也可以从索引中满足Name上的谓词,限制基础表中页面的查找次数,它需要为返回的行执行,以获取行的"所有"列.


对于SQL Server 2008或更高版本,我会考虑过滤索引...取决于Status ='New'的基数(即,如果满足谓词的行Status='New'是表的相对较小的子集.

  CREATE NONCLUSTERED INDEX Documents_FIX 
  ON Documents (Status, DateCreated, Name)
  WHERE Status = 'New'
Run Code Online (Sandbox Code Playgroud)

我还会修改要指定的查询 ORDER BY Status, DateCreated, Name

为了使order by子句与索引匹配,它并不真正改变返回行的顺序.


作为一个更复杂的替代方案,我会考虑添加一个持久计算列并在其上添加一个过滤索引

  ALTER TABLE Documents
    ADD new_none_date_created AS
      CASE 
        WHEN Status = 'New' AND COALESCE(Name,'') IN ('','None') THEN DateCreated 
        ELSE NULL
      END
    PERSISTED
  ;  

  CREATE NONCLUSTERED INDEX Documents_FIXP 
  ON Documents (new_none_date_created)
  WHERE new_none_date_created IS NOT NULL
  ;
Run Code Online (Sandbox Code Playgroud)

然后可以重写查询:

  SELECT TOP 50 *
    FROM Documents
   WHERE new_none_date_created IS NOT NULL
   ORDER BY new_none_date_created 
  ;
Run Code Online (Sandbox Code Playgroud)