sql - 避免索引计划中的排序运算符

标签 sql sql-server-2008 indexing sql-server-2012 database-tuning

我有两个表[LogTable]和[LogTable_Cross]。

以下是填充它们的架构和脚本:

 --Main Table

 CREATE TABLE [dbo].[LogTable]
    (
      [LogID] [int] NOT NULL
                    IDENTITY(1, 1) ,
      [DateSent] [datetime] NULL,
    )
 ON [PRIMARY]
GO
 ALTER TABLE [dbo].[LogTable] ADD CONSTRAINT [PK_LogTable] PRIMARY KEY CLUSTERED  ([LogID]) ON [PRIMARY]
GO
 CREATE NONCLUSTERED INDEX [IX_LogTable_DateSent] ON [dbo].[LogTable] ([DateSent] DESC) ON [PRIMARY]
GO
 CREATE NONCLUSTERED INDEX [IX_LogTable_DateSent_LogID] ON [dbo].[LogTable] ([DateSent] DESC) INCLUDE ([LogID]) ON [PRIMARY]
GO


--Cross table

 CREATE TABLE [dbo].[LogTable_Cross]
    (
      [LogID] [int] NOT NULL ,
      [UserID] [int] NOT NULL
    )
 ON [PRIMARY]
GO
 ALTER TABLE [dbo].[LogTable_Cross] WITH NOCHECK ADD CONSTRAINT [FK_LogTable_Cross_LogTable] FOREIGN KEY ([LogID]) REFERENCES [dbo].[LogTable] ([LogID])
GO
 CREATE NONCLUSTERED INDEX [IX_LogTable_Cross_UserID_LogID]
 ON [dbo].[LogTable_Cross] ([UserID])
 INCLUDE ([LogID])
GO


-- Script to populate them
 INSERT INTO [LogTable]
        SELECT TOP 100000
                DATEADD(day, ( ABS(CHECKSUM(NEWID())) % 65530 ), 0)
        FROM    sys.sysobjects
                CROSS JOIN sys.all_columns


 INSERT INTO [LogTable_Cross]
        SELECT  [LogID] ,
                1
        FROM    [LogTable]
        ORDER BY NEWID()

 INSERT INTO [LogTable_Cross]
        SELECT  [LogID] ,
                2
        FROM    [LogTable]
        ORDER BY NEWID()

 INSERT INTO [LogTable_Cross]
        SELECT  [LogID] ,
                3
        FROM    [LogTable]
        ORDER BY NEWID()


GO

我想从日期表中选择所有给定用户ID(将从交叉表LogTable_Cross中检查用户ID)的日志(来自LogTable)。
SELECT  DI.LogID              
FROM    LogTable DI              
        INNER JOIN LogTable_Cross DP ON DP.LogID = DI.LogID  
        WHERE  DP.UserID = 1  
ORDER BY DateSent DESC

运行此查询后,这是我的执行计划:
enter image description here

如您所见,有一个排序运算符正在起作用,这可能是因为以下行“ORDER BY DateSent DESC”

我的问题是,即使我在表上应用了以下索引,为什么排序运算符也会出现在计划中
GO
 CREATE NONCLUSTERED INDEX [IX_LogTable_DateSent] ON [dbo].[LogTable] ([DateSent] DESC) ON [PRIMARY]
GO
 CREATE NONCLUSTERED INDEX [IX_LogTable_DateSent_LogID] ON [dbo].[LogTable] ([DateSent] DESC) INCLUDE ([LogID]) ON [PRIMARY]
GO

另一方面,如果我删除联接并以这种方式编写查询,则:
SELECT  DI.LogID              
FROM    LogTable DI              
  --      INNER JOIN LogTable_Cross DP ON DP.LogID = DI.LogID  
        --WHERE  DP.UserID = 1  
ORDER BY DateSent DESC

计划更改为

enter image description here

即删除了排序运算符,并且计划显示我的查询正在使用我的非聚集索引。

这是一种即使我使用联接也可以在查询计划中删除“排序”运算符的方法。

编辑:

我走得更远,将“最大并行度”限制为1

enter image description here

再次运行以下查询:
SELECT  DI.LogID              
FROM    LogTable DI              
        INNER JOIN LogTable_Cross DP ON DP.LogID = DI.LogID  
        WHERE  DP.UserID = 1  
ORDER BY DateSent DESC

并且该计划仍具有该Sort运算符:

enter image description here

编辑2

即使我有以下建议的索引:
 CREATE NONCLUSTERED INDEX [IX_LogTable_Cross_UserID_LogID_2]
 ON [dbo].[LogTable_Cross] ([UserID], [LogID])

该计划仍具有Sort运算符:
enter image description here

最佳答案

您的第二个查询不包含UserId条件,因此它不是等效查询。 LogTable上的索引未涵盖第一个查询的原因是事实,即它们中不存在UserId(并且您也需要执行连接)。因此,SQL Server必须联接表(哈希联接,合并联接或嵌套循环联接)。 SQL Server正确选择哈希联接,因为中间结果很大,并且它们没有根据LogID进行排序。如果您给他们根据LogID排序的中间结果(您的第二次编辑),那么他将使用合并连接,但是,仍然需要根据DateSend进行排序。没有排序的唯一解决方案是创建索引的物化 View :

CREATE VIEW vLogTable
WITH SCHEMABINDING
AS
   SELECT  DI.LogID, DI.DateSent, DP.UserID           
   FROM dbo.LogTable DI              
   INNER JOIN dbo.LogTable_Cross DP ON DP.LogID = DI.LogID  

CREATE UNIQUE CLUSTERED INDEX CIX_vCustomerOrders 
   ON dbo.vLogTable(UserID, DateSent, LogID);

该 View 必须与noexpand提示一起使用,因此优化器可以找到CIX_vCustomerOrders索引:
SELECT  LogID              
FROM dbo.vLogTable   WITH(NOEXPAND)
    WHERE  UserID = 1  
ORDER BY DateSent DESC

此查询与您的第一个查询等效。如果您插入以下行,则可以检查正确性:
INSERT INTO LogTable VALUES (CURRENT_TIMESTAMP)

那么我的查询仍然返回正确的结果(10000行),但是,第二个查询返回10001行。您可以尝试删除或插入一些其他行,并且该 View 仍然是最新的,并且您从我的查询中收到了正确的结果。

关于sql - 避免索引计划中的排序运算符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43490920/

相关文章:

search - nGram 部分匹配和限制 nGram 导致多字段查询

indexing - Lua中返回表的索引

sql - 如何通过注册表查找sql​​ server实例的版本和版本

sql - 将 [每行一个单词] 与 [每行多个单词] 的短语行连接起来

python - 从函数返回数组索引

mysql - 如何在类(class)分组时获得最大出生日期?

sql-server - 关键字 'IF' 附近的语法不正确

sql - 选择列是两个可能值之一

mysql - 将 AVG(整数) 与类别的 AVG(整数) 进行比较

php - 一个 SQL 查询,还是一个循环中的多个?