查询显示排序成本,即使需要的索引可用

The*_*war 5 performance sql-server optimization query-performance

这个问题来自 SO,如果它移动到 DBA.SE ..

下面是测试数据:

--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
Run Code Online (Sandbox Code Playgroud)

当我们使用下面的简单查询时,

SELECT  DI.LogID              
FROM    LogTable DI              
        INNER JOIN LogTable_Cross DP ON DP.LogID = DI.LogID  
        WHERE  DP.UserID = 1  
ORDER BY DateSent DESC
Run Code Online (Sandbox Code Playgroud)

查询显示排序

在此处输入图片说明

根据我的理解,应该避免排序成本,因为我们有所需的索引

 CREATE NONCLUSTERED INDEX [IX_LogTable_DateSent] ON [dbo].[LogTable] ([DateSent] DESC) ON [PRIMARY]
Run Code Online (Sandbox Code Playgroud)

并且深入研究计划显示使用了相同的索引..

我的问题是: 1.
为什么排序成本仍然存在
2. 究竟是什么ordered property is true意思。

我所做的观察/工作很少:
1.Ordered 属性设置为 false

所以我重写了如下查询

   SELECT  DI.LogID              
    FROM    LogTable DI              
            INNER JOIN LogTable_Cross DP ON DP.LogID = DI.LogID  
            WHERE  DP.UserID = 1  and di.datesent is not null
    ORDER BY DateSent DESC
Run Code Online (Sandbox Code Playgroud)

像上面那样重写,使有序属性为真,但仍然存在排序

最近,我可以找到 Paul White 的这篇文章:两个索引提示的故事,在这篇文章中,下面的 Point 阐明了为什么会发生这种情况

对于非常大的表,优化器可能会计算出 IAM 驱动的扫描可能比额外排序所消耗的时间节省更多时间,并且将选择具有无序扫描 + 排序特征的计划。这是一种启发式优化:优化器对 inde 的实际碎片级别一无所知

但是这个查询需要排序,它不应该满足 IAM 扫描的条件

如果您需要更多详细信息,请告诉我

Joe*_*ish 5

在查询计划中,ordered 属性设置为 true 意味着未完成 IAM 驱动的扫描。根据索引定义以逻辑顺序读取数据。

散列连接不保留顺序。您的第一个查询有一个散列连接,因此最后SORT需要显式连接。避免查询中的排序的一种方法是使用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
OPTION (MAXDOP 1, LOOP JOIN, FORCE ORDER, NO_PERFORMANCE_SPOOL);
Run Code Online (Sandbox Code Playgroud)

错误的查询计划

这是一个非常低效的查询计划,因为没有在LogTable_Cross表上定义正确的索引。对该表进行了查找,但只有一个 UserId 列:

不好找

过滤LogID是在连接本身中完成的。我可以创建一个更好地支持我正在寻找的查询计划的索引:

CREATE INDEX IX_LogTable_Cross ON LogTable_Cross (LogID, UserID);
Run Code Online (Sandbox Code Playgroud)

创建该索引并不能保证没有排序的查询计划。SQL Server 可能估计具有排序的计划的成本较低。但是,如果我消除了除 之外的所有提示,LOOP JOIN那么我会得到一个没有排序的合理高效的查询计划:

好的查询计划