SQL优化(1)
背景: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,均明显下降;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!