9

我有两个表 [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)(用户ID将从交叉表LogTable_Cross中检查),日期为desc。

SELECT  DI.LogID              
FROM    LogTable DI              
        INNER JOIN LogTable_Cross DP ON DP.LogID = DI.LogID  
        WHERE  DP.UserID = 1  
ORDER BY DateSent DESC

运行此查询后,这是我的执行计划: 在此处输入图像描述

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

我的问题是,即使我在表上应用了以下索引,为什么该 Sort 运算符仍会出现在计划中

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

计划更改为

在此处输入图像描述

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

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

编辑

我更进一步,将“最大并行度”限制为 1

在此处输入图像描述

再次运行以下查询:

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 运算符:

在此处输入图像描述

编辑 2

即使我有以下建议的索引:

 CREATE NONCLUSTERED INDEX [IX_LogTable_Cross_UserID_LogID_2]
 ON [dbo].[LogTable_Cross] ([UserID], [LogID])

该计划仍然有排序运算符: 在此处输入图像描述

4

3 回答 3

2

您的第二个查询不包含 UserId 条件,因此它不是等效查询。LogTable 上的索引未涵盖第一个查询的原因是 UserId 不存在于它们中(并且您还需要执行连接)。因此,SQL Server 必须加入表(Hash Join、Merge Join 或 Nested-Loop join)。SQL Server 正确选择了 Hash Join,因为中间结果很大并且它们没有根据 LogID 排序。如果您给他们根据 LogID 排序的中间结果(您的第二次编辑),那么他使用合并连接,但是,仍然需要根据 DateSend 排序。没有排序的唯一解决方案是创建索引物化视图:

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);

该视图必须与 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 行。您可以尝试删除或插入其他一些行,并且视图仍然是最新的,并且您从我的查询中收到正确的结果。

于 2017-04-19T14:03:43.687 回答
1

我想原因可能在这里:

CREATE NONCLUSTERED INDEX [IX_LogTable_Cross_UserID_LogID]
 ON [dbo].[LogTable_Cross] ([UserID])
 INCLUDE ([LogID])

您的表在 LogID 上没有索引。但此列用于JOIN. INCLUDE LogID并不意味着这个索引是可搜索的LogID。这只是快一点,如果您搜索UserId并且您需要相应的LogID(无需查找)

当您通过 LogID 加入时,对列表进行预排序应该是最快的,因为没有可用的索引......

如果空间无关紧要(并且插入/更新性能,您可能会添加一个索引,反之亦然,但我建议在第一个位置使用带有 LogId 的两列聚集键,并且 - 如果需要 - 一个简单的非聚集索引用户身份。

于 2017-04-19T09:29:49.350 回答
1

由于前面步骤中的并行性,您在加入时进行了排序操作。当 SQL Server 在多个线程中处理记录时,不再确定顺序。每个线程只是将结果推送到管道中的下一个项目(在您的情况下为哈希匹配)。

由于订单未确定并且您正在请求订单,因此 SQL Server 必须对结果进行排序。

您可以尝试添加MAXDOP = 1提示以强制 SQL Server 仅使用一个线程运行查询。在这种情况下,这可能会有所帮助,但也会导致性能下降。

可以使用索引扫描来满足第二个查询,并且索引是有序的,并且该顺序与请求的顺序相同。索引中的记录(键)按定义排序。SQL Server 猜测在一个线程上运行查询并仅使用索引读取数据比使用多个线程读取数据并稍后对其进行排序更有益。

于 2017-04-19T09:14:46.760 回答