背景:StoreNotifyMainTask為主表,StoreNotifySubTask為子表,應用幾秒鍾關聯查詢一下,根據主、子表的條件查出top 100;
目前主表記錄數648W,單表符合條件的記錄647W(基本全部符合條件)
子表記錄數425W,單表符合條件的記錄106W
主表id列與子表maintaskid為邏輯主外鍵關系
由於子表條件固定,於是創建篩選索引

1 CREATE NONCLUSTERED INDEX [idxw_StoreNotifySubTask_RetryNum_yn_MainTaskId_inc] ON [dbo].[StoreNotifySubTask] 2 ( 3 [RetryNum] ASC, 4 [YN] ASC 5 ) 6 INCLUDE ( [MainTaskId]) 7 WHERE ([RetryNum]<(3) AND [NotifyState]=(0) AND [yn]=(1))
初始的SQL如下:

1 SELECT TOP 100 2 sub.Id , 3 sub.SubscriberId , 4 sub.MainTaskId , 5 sub.Pin , 6 sub.BlogPin , 7 sub.SkuId , 8 sub.SkuName , 9 sub.Wpid1 , 10 sub.Wpid2 , 11 sub.Wpid3 , 12 sub.Email , 13 sub.PhoneNo , 14 sub.Price , 15 sub.SendPrice , 16 sub.RetryNum , 17 sub.AddressId , 18 sub.CreateTime , 19 ISNULL(sub.MessageTag, 0) AS MessageTag , 20 sub.UpdateTime , 21 sub.SendTime , 22 sub.NotifyState , 23 sub.YN , 24 sub.Ext , 25 sub.SkuPicUrl , 26 sub.SubscriberTime 27 FROM StoreNotifySubTask sub WITH ( NOLOCK) 28 INNER JOIN StoreNotifyMainTask main ( NOLOCK ) ON sub.MainTaskId = main.Id 29 WHERE main.TaskState = 2 30 AND main.YN = 1 31 AND sub.NotifyState = 0 32 AND sub.RetryNum < 3 33 AND sub.YN = 1
執行計划:子表無法使用篩選索引
(0 行受影響)
表'StoreNotifyMainTask'。掃描計數0,邏輯讀取4333270 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。
表'StoreNotifySubTask'。掃描計數1,邏輯讀取209314 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。
SQL Server 執行時間:
CPU 時間= 10592 毫秒,占用時間= 11298 毫秒。
強制使用篩選索引,導致子表邏輯讀上升;
(0 行受影響)
表'StoreNotifyMainTask'。掃描計數0,邏輯讀取4333270 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。
表'StoreNotifySubTask'。掃描計數1,邏輯讀取4338240 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。
SQL Server 執行時間:
CPU 時間= 11965 毫秒,占用時間= 12014 毫秒。
優化思路:盡管子表中單表符合條件的記錄有106W,但按照maintaskid分組查詢發現,一個maintaskid有大量的子記錄,實際符合條件的maintaskid只有130個左右
於是,先從子表中查詢符合條件的的maintaskid(做group by),然后驗證這些maintaskid在主表中是否符合條件,再回到子表中按照最終篩選的maintaskid做自關聯;
由於業務邏輯上可能出現子表中同一個maintaskid也包含不符合條件的記錄,因此最后一步的按照maintaskid的子表自關聯,還需要加上之前子表的條件作為限定;
優化后的SQL:

; WITH cte AS ( SELECT sub.MainTaskId FROM StoreNotifySubTask sub WITH ( NOLOCK ) WHERE sub.NotifyState = 0 AND sub.RetryNum < 3 AND sub.YN = 1 GROUP BY MainTaskId ) ,cte1 AS( SELECT MainTaskId FROM cte where EXISTS(SELECT id FROM StoreNotifyMainTask main ( NOLOCK ) WHERE cte.MainTaskId = main.Id AND main.TaskState = 2 AND main.YN = 1) ) select TOP 100 sub.Id , sub.SubscriberId , sub.MainTaskId , sub.Pin , sub.BlogPin , sub.SkuId , sub.SkuName , sub.Wpid1 , sub.Wpid2 , sub.Wpid3 , sub.Email , sub.PhoneNo , sub.Price , sub.SendPrice , sub.RetryNum , sub.AddressId , sub.CreateTime , ISNULL(sub.MessageTag, 0) AS MessageTag , sub.UpdateTime , sub.SendTime , sub.NotifyState , sub.YN , sub.Ext , sub.SkuPicUrl , sub.SubscriberTime FROM StoreNotifySubTask sub WITH ( NOLOCK ) JOIN cte1 ON sub.MainTaskId=cte1.maintaskid WHERE sub.NotifyState = 0 AND sub.RetryNum < 3 AND sub.YN = 1
上述SQL中,可以將cte和cte1合並,改為:

1 ; WITH cte AS ( 2 SELECT sub.MainTaskId 3 FROM StoreNotifySubTask sub WITH ( NOLOCK ) 4 WHERE sub.NotifyState = 0 5 AND sub.RetryNum < 3 6 AND sub.YN = 1 7 AND EXISTS(SELECT id FROM StoreNotifyMainTask main ( NOLOCK ) WHERE sub.MainTaskId = main.Id AND main.TaskState = 2 8 AND main.YN = 1) 9 GROUP BY MainTaskId 10 )
對比執行結果:
優化前:
(0 行受影響)
表'StoreNotifyMainTask'。掃描計數0,邏輯讀取4333270 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。
表'StoreNotifySubTask'。掃描計數1,邏輯讀取209361 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。
SQL Server 執行時間:
CPU 時間= 12465 毫秒,占用時間= 12599 毫秒。
優化后:
(0 行受影響)
表'Worktable'。掃描計數0,邏輯讀取0 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。
表'StoreNotifySubTask'。掃描計數1,邏輯讀取4970 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。
表'StoreNotifyMainTask'。掃描計數0,邏輯讀取537 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。
表'Worktable'。掃描計數0,邏輯讀取0 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。
SQL Server 執行時間:
CPU 時間= 312 毫秒,占用時間= 323 毫秒。
執行時間、IO,均明顯下降;