我有两个表[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
我想从datesent desc中选择已经给出userid(用户ID将从交叉表LogTable_Cross检查)的所有日志(来自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
正如您所看到的那样,有一个排序运算符即将发挥作用,这可能是因为以下行“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])
答案 0 :(得分:2)
您的第二个查询不包含UserId条件,因此它不是等效查询。 LogTable上的索引未涵盖第一个查询的原因是UserId不存在于其中(并且您还需要执行连接)。因此,SQL Server必须连接表(散列连接,合并连接或嵌套循环连接)。 SQL Server正确选择了哈希联接,因为中间结果很大,并且它们没有根据LogID进行排序。如果你给他们根据LogID(你的第二个编辑)排序的中间结果然后他使用合并连接,但是,根据DateSend排序需要stil。没有排序的唯一解决方案是创建索引的物化视图:
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行。您可以尝试删除或插入其他一些行,并且视图仍然是最新的,并且您从我的查询中收到了正确的结果。
答案 1 :(得分:1)
由于前面步骤中的并行性,在进行连接时会进行排序操作。当SQL Server处理多个线程中的记录时,不再确定顺序。每个线程只是将结果推送到管道中的下一个项目(在你的情况下是哈希匹配)。
由于订单未确定且您要求订购,因此SQL Server必须对结果进行排序。
您可以尝试添加MAXDOP = 1
提示以强制SQL Server仅使用一个线程运行查询。在这种情况下,这可能有所帮助,但也可能导致性能下降。
使用索引扫描可以满足第二个查询,并且索引是有序的,并且该顺序与请求的顺序相同。索引中的记录(键)按定义排序。 SQL Server猜测在一个线程上运行查询而只是使用索引读取数据比使用多个线程读取数据并在以后对它们进行排序更有利。
答案 2 :(得分: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位于第一个位置,并且 - 如果需要 - 是UserId上的一个简单的非聚集索引。