8

我有兩個表[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 

我要選擇那些日誌(從LogTable),它給了用戶ID與datesent遞減(用戶ID將從交叉表LogTable_Cross選中) 。

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 

和計劃仍然有那個排序算子:

enter image description here

編輯2

即使我有以下指標的建議:

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

計劃仍然有排序操作: enter image description here

+0

這個問題是非常有趣,非常好框架 – TheGameiswar

+0

我已經重新評論了DBA.SE與我的觀察以及:https://dba.stackexchange.com/questions/171476/query-shows-sort-cost-even -when-required-index-is-available – TheGameiswar

回答

1

你有由於前面步驟中的並行性,當您有連接時進行排序操作。當SQL Server處理多個線程中的記錄時,訂單不再被確定。每個線程只是將結果推送到管道中的下一個項目(在你的情況下是哈希匹配)。

由於訂單未確定且您要求訂單,因此SQL Server必須對結果進行排序。

您可以嘗試添加MAXDOP = 1提示以強制SQL Server僅使用一個線程運行查詢。在這種情況下,這可能會有所幫助,但也會導致性能下降。

使用索引掃描可以滿足第二個查詢,並且該索引是有序的,並且該順序與請求的順序相同。索引中的記錄(鍵)按照定義排序。 SQL Server猜測,在一個線程上運行查詢並僅使用索引讀取數據比使用多線程讀取數據並稍後對它們進行排序更有利。

+0

請檢查我的編輯。 – Raghav

+0

對不起,我花了一些時間回來,但我認爲** plain_talk **和** Shnugo **都添加了您需要的額外信息。 – Pred

1

我想原因可能是在這裏:

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

您的表不會對LOGID的索引。但是此列用於JOININCLUDE LogID並不意味着,該索引可以搜索LogID。如果你搜索UserId並且你需要相應的LogID(不需要查看這個)

當你加入LogID時,它應該是最快的,因爲它是預先排序的沒有索引可用...

如果空間沒有關係(和插入/更新性能,可以添加一個索引反之亦然,但我會建議做使用在同一個LOGID 兩欄聚集鍵第一個位置和 - 如果需要的話 - 在UserId上的一個簡單的非聚集索引。

+0

請參閱我的編輯2 – Raghav

+0

@Raghav您的新索引 - 再次 - 具有UserId在第一個地方...首先嚐試LogId ...您真正的表真的只包含兩列嗎?一個集羣PK(LogId,UserId)存儲你的物理排序數據。聚簇鍵**是表本身**。它是**包括所有其他列** ... – Shnugo

+0

我創建了以下索引「創建無記錄索引[IX_LogTable_Cross_LogID_UserID] ON [dbo]。[LogTable_Cross]([LogID],[UserID])」 但「Sort 「運營商仍然在那裏。 – Raghav

2

T他的第二個查詢不包含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行。您可以嘗試刪除或插入其他一些行,並且該視圖仍然是最新的,並且您可以從我的查詢中收到正確的結果。

+0

您能否用特定於我的數據的示例來詳細說明您的答案? – Raghav

+0

我做到了。它回答你的問題嗎? –