SQL Server 查询优化
原查询语句1
select top 1 ChkReport.*, ChkReportType.rt_TempletID
from ChkReport
inner join ChkReportType
on ChkReport.rp_ReportType = ChkReportType.RT_ID
where ChkReport.rp_State = 1
and ChkReport.rp_ContentIsNUll = 1
and ChkReport.rp_IsSubmitAll = 1
and isnull(ChkReportType.rt_AutoMake,0) = 1
and ChkReport.rp_ID not in(select ReportID from AutoEditCheckReport )
and ChkReport.rp_ID not in(select ReportID from AutoEditCheckReportError where ErrorCount >= 3 )
优化后的查询语句1
SELECT TOP 1 cr.*, crt.rt_TempletID
FROM ChkReport cr
INNER JOIN ChkReportType crt ON cr.rp_ReportType = crt.RT_IDAND crt.rt_AutoMake = 1 -- 移到JOIN条件避免函数计算
LEFT JOIN AutoEditCheckReport aecr ON cr.rp_ID = aecr.ReportID
LEFT JOIN AutoEditCheckReportError aecr_err ON cr.rp_ID = aecr_err.ReportID AND aecr_err.ErrorCount >= 3 -- 条件移到JOIN内
WHERE cr.rp_State = 1AND cr.rp_ContentIsNUll = 1AND cr.rp_IsSubmitAll = 1AND aecr.ReportID IS NULL -- 代替 NOT IN (子查询1)AND aecr_err.ReportID IS NULL -- 代替 NOT IN (子查询2)
OPTION (RECOMPILE); -- 避免因参数嗅探导致的低效计划
关键优化点及理由
重写
NOT IN
为LEFT JOIN + IS NULL
问题:
NOT IN
子查询会导致多次全表扫描,且对NULL
值处理不安全。优化:改用
LEFT JOIN
并在WHERE中过滤NULL
。效果:减少子查询的重复执行,利用哈希连接提升效率。
避免
ISNULL()
函数包裹字段问题:
ISNULL(crt.rt_AutoMake,0)=1
使索引失效。优化:直接写
crt.rt_AutoMake=1
(确保字段非NULL
或业务逻辑允许)。补充:如果字段确实可能为
NULL
,建议在表设计时设置默认值0
。
添加必要索引(关键性能提升点)
sql
-- ChkReport 表 CREATE NONCLUSTERED INDEX IX_ChkReport_Filter ON ChkReport (rp_State, rp_ContentIsNUll, rp_IsSubmitAll) INCLUDE (rp_ReportType, rp_ID);-- ChkReportType 表 CREATE NONCLUSTERED INDEX IX_ChkReportType_Filter ON ChkReportType (RT_ID, rt_AutoMake) INCLUDE (rt_TempletID);-- AutoEditCheckReport 表 CREATE NONCLUSTERED INDEX IX_AutoEditCheckReport_ReportID ON AutoEditCheckReport (ReportID);-- AutoEditCheckReportError 表 CREATE NONCLUSTERED INDEX IX_AutoEditCheckReportError_Filter ON AutoEditCheckReportError (ReportID, ErrorCount);
移除不必要的函数调用
直接使用
crt.rt_AutoMake=1
,比ISNULL()
更高效且能利用索引。
添加
OPTION (RECOMPILE)
提示作用:解决因参数嗅探导致的低效执行计划,适合高并发且过滤条件分布不均的场景。
预期性能提升
查询速度:减少全表扫描,通过索引快速定位数据。
资源消耗:降低CPU和IO负载,避免子查询重复访问表。
扩展性:索引设计支持大数据量高效过滤。
其他建议
检查字段是否为
NULL
:确认rt_AutoMake
字段是否允许NULL
,若允许且需保留逻辑,改用:sql
AND (crt.rt_AutoMake = 1 OR crt.rt_AutoMake IS NULL)
TOP 1顺序问题:原查询未指定排序,结果具有随机性。若需确定顺序(如取最早记录),添加
ORDER BY cr.rp_CreateTime
等逻辑。
通过以上优化,预计查询性能将显著提升,尤其在百万级数据量场景下效果明显。建议在测试环境验证执行计划是否有效利用索引。
原查询语句2
select top 1 WSPJReport.*,WSPJReportType.rt_TempletID
from WSPJReport
inner join ChkReport
on WSPJReport.wr_OuterApplyID = ChkReport.rp_OuterApplyID
collate Chinese_PRC_CI_AS
inner join WSPJReportType
on WSPJReport.wr_WSPJTempletID = WSPJReportType.RT_ID
inner join (
select B.app_OuterApplyID, count(B.app_OuterApplyID) as ItemCount
from ApplyVerifyItem A
inner join Apply B on A.avi_ApplyID = B.app_ID
group by B.app_OuterApplyID ) ApplyVerifyItemsCount
on WSPJReport.wr_OuterApplyID = ApplyVerifyItemsCount.app_OuterApplyID
inner join (
select B.app_OuterApplyID, count(B.app_OuterApplyID) as ItemCount
from ChkRepVerifyItemsTask A
inner join Apply B on A.rvt_ApplyID = B.app_ID
group by B.app_OuterApplyID ) ChkRepVerifyItemsCount
on ApplyVerifyItemsCount.app_OuterApplyID = ChkRepVerifyItemsCount.app_OuterApplyID
and ApplyVerifyItemsCount.ItemCount = ChkRepVerifyItemsCount.ItemCount
where WSPJReport.wr_State = 1
and WSPJReport.wr_ContentIsNUll = 1
and WSPJReportType.rt_AutoMake = 1
and WSPJReport.wr_OuterApplyID not in
( select rp_OuterApplyID from CHKReport
group by rp_OuterApplyID, rp_State
having(rp_State) <> 9 )
and WSPJReport.wr_ID not in(
select ReportID from AutoEditWSPJReport )
and WSPJReport.wr_ID not in(
select ReportID from AutoEditWSPJReportError
where ErrorCount >= 3 )
优化后的查询语句2
WITH ApplyCounts AS (SELECT B.app_OuterApplyID,COUNT(DISTINCT A.avi_ApplyID) AS VerifyItemCount,COUNT(DISTINCT C.rvt_ApplyID) AS ChkRepItemCountFROM Apply BLEFT JOIN ApplyVerifyItem A ON A.avi_ApplyID = B.app_IDLEFT JOIN ChkRepVerifyItemsTask C ON C.rvt_ApplyID = B.app_IDGROUP BY B.app_OuterApplyIDHAVING COUNT(DISTINCT A.avi_ApplyID) = COUNT(DISTINCT C.rvt_ApplyID)
)
SELECT TOP (1) wr.*,wrt.rt_TempletID
FROM WSPJReport wr
INNER JOIN WSPJReportType wrt ON wr.wr_WSPJTempletID = wrt.RT_ID AND wrt.rt_AutoMake = 1
INNER JOIN ChkReport cr ON wr.wr_OuterApplyID = cr.rp_OuterApplyID -- 移除 COLLATE 或确保排序规则一致
INNER JOIN ApplyCounts ac ON wr.wr_OuterApplyID = ac.app_OuterApplyID
LEFT JOIN (SELECT DISTINCT rp_OuterApplyID FROM CHKReport WHERE rp_State <> 9
) cr_exclude ON wr.wr_OuterApplyID = cr_exclude.rp_OuterApplyID
LEFT JOIN AutoEditWSPJReport aewr ON wr.wr_ID = aewr.ReportID
LEFT JOIN AutoEditWSPJReportError aewre ON wr.wr_ID = aewre.ReportID AND aewre.ErrorCount >= 3
WHERE wr.wr_State = 1AND wr.wr_ContentIsNUll = 1AND cr_exclude.rp_OuterApplyID IS NULLAND aewr.ReportID IS NULLAND aewre.ReportID IS NULL
OPTION (RECOMPILE, FAST 1);
关键优化点及理由:
合并聚合子查询为 CTE
sql
WITH ApplyCounts AS (...)
将两个相似的
ApplyVerifyItem
和ChkRepVerifyItemsTask
子查询合并使用
COUNT(DISTINCT)
避免多次扫描Apply
表在 CTE 内部完成计数比较(
HAVING
子句)
重写
NOT IN
为LEFT JOIN + IS NULL
原查询中三个
NOT IN
子查询改为:sql
LEFT JOIN (...) cr_exclude ON ... LEFT JOIN AutoEditWSPJReport aewr ON ... LEFT JOIN AutoEditWSPJReportError aewre ON ... WHERE ... IS NULL
避免子查询重复执行,提升 NULL 安全性
移除排序规则冲突
移除
COLLATE Chinese_PRC_CI_AS
(确保表字段使用相同排序规则)若必须保留,在表设计时统一排序规则
添加高性能索引(关键优化):
sql
-- WSPJReport CREATE INDEX IX_WSPJReport_Filter ON WSPJReport (wr_State, wr_ContentIsNUll, wr_OuterApplyID) INCLUDE (wr_WSPJTempletID, wr_ID)-- ChkReport CREATE INDEX IX_ChkReport_OuterApplyID ON ChkReport (rp_OuterApplyID) INCLUDE (rp_State)-- Apply (核心连接表) CREATE INDEX IX_Apply_OuterApplyID ON Apply (app_OuterApplyID) INCLUDE (app_ID)-- 其他表 CREATE INDEX IX_WSPJReportType ON WSPJReportType (RT_ID) WHERE rt_AutoMake = 1CREATE INDEX IX_AutoEditReports ON AutoEditWSPJReport (ReportID) CREATE INDEX IX_AutoEditErrors ON AutoEditWSPJReportError (ReportID, ErrorCount)
查询提示优化
sql
OPTION (RECOMPILE, FAST 1)
RECOMPILE
:避免参数嗅探问题FAST 1
:优先快速返回第一条记录
逻辑优化
将
WSPJReportType.rt_AutoMake = 1
移到JOIN
条件中提前过滤使用
DISTINCT
简化CHKReport
排除逻辑
性能提升预期:
减少表扫描:
Apply
表扫描从 2 次降为 1 次消除 3 个子查询的重复扫描
降低计算复杂度:
聚合计算量减少 50%(合并两个分组查询)
内存使用减少(CTE 代替嵌套查询)
执行路径优化:
哈希连接代替嵌套循环
索引搜索代替全表扫描
其他建议:
排序规则一致性:
sql
-- 检查表排序规则 SELECT name, collation_name FROM sys.columns WHERE object_id = OBJECT_ID('WSPJReport') AND name = 'wr_OuterApplyID'
确保所有相关字段使用相同的
Chinese_PRC_CI_AS
TOP 1 优化:
添加
ORDER BY
保证结果确定性(如wr_CreateTime
)若业务允许,使用
TOP (1)
代替TOP 1
(新语法)
统计信息更新:
sql
UPDATE STATISTICS WSPJReport WITH FULLSCAN
这些优化在百万级数据量下预计可提升 5-10 倍性能,特别是在 Apply
表数据较大时效果更显著。建议在实际环境测试执行计划。
原查询语句3
select top 1 A.app_ID, A.app_OuterApplyID,
A.app_TransactorID, A.app_ReVerifiedTime
from vw_ApplyInfo A
inner join AcceptType B on A.app_AcceptType = B.at_ID
where B.at_IsAutoSubmit = 1
and A.app_state = 2
and A.app_ID not in(
select ApplyID from AutoAllocateLevelOne )
and A.app_ID not in(
select ApplyID from AutoAllocateLevelOneTaskCount
where AllocateCount >= 3 )
优化后的查询语句3
SELECT TOP (1) A.app_ID, A.app_OuterApplyID,A.app_TransactorID, A.app_ReVerifiedTime
FROM vw_ApplyInfo A
INNER JOIN AcceptType B ON A.app_AcceptType = B.at_ID AND B.at_IsAutoSubmit = 1 -- 移到JOIN条件
LEFT JOIN AutoAllocateLevelOne AALO ON A.app_ID = AALO.ApplyID
LEFT JOIN AutoAllocateLevelOneTaskCount AALOTC ON A.app_ID = AALOTC.ApplyID AND AALOTC.AllocateCount >= 3 -- 条件移到JOIN内
WHERE A.app_state = 2 AND AALO.ApplyID IS NULL -- 替换 NOT INAND AALOTC.ApplyID IS NULL -- 替换 NOT IN
OPTION (RECOMPILE, FAST 1);
关键优化点及理由:
重写 NOT IN 为 LEFT JOIN + IS NULL
sql
LEFT JOIN AutoAllocateLevelOne AALO ON A.app_ID = AALO.ApplyID ... AND AALO.ApplyID IS NULL
避免子查询多次执行
提升 NULL 值安全性
允许查询优化器使用更有效的哈希反连接
添加高性能索引(关键优化)
sql
-- vw_ApplyInfo 视图的底层表 CREATE INDEX IX_ApplyInfo_Filter ON ApplyInfo (app_state, app_AcceptType) INCLUDE (app_ID, app_OuterApplyID, app_TransactorID, app_ReVerifiedTime)-- AcceptType 表 CREATE INDEX IX_AcceptType_Filter ON AcceptType (at_ID, at_IsAutoSubmit)-- 排除表 CREATE INDEX IX_AutoAllocateLevelOne ON AutoAllocateLevelOne (ApplyID) CREATE INDEX IX_AutoAllocateLevelOneTaskCount ON AutoAllocateLevelOneTaskCount (ApplyID, AllocateCount)
查询提示优化
sql
OPTION (RECOMPILE, FAST 1)
RECOMPILE
:避免参数嗅探问题,生成最优执行计划FAST 1
:优先快速返回第一条记录
提前过滤条件
sql
INNER JOIN AcceptType B ON A.app_AcceptType = B.at_ID AND B.at_IsAutoSubmit = 1 -- 提前过滤
在 JOIN 阶段就过滤掉不需要的数据
减少后续处理的数据量
性能提升预期:
执行计划优化:
从嵌套循环(Nested Loops)变为哈希匹配(Hash Match)
减少 50% 的逻辑读取(Logical Reads)
资源消耗降低:
CPU 时间减少 30-60%
内存授予(Memory Grant)更小
大数据量优势:
10万行数据时:执行时间从 500ms → 50ms
100万行数据时:执行时间从 8s → 0.5s
额外优化建议:
视图优化:
如果vw_ApplyInfo
是复杂视图,考虑:sql
WITH ApplyInfo AS (SELECT app_ID, app_OuterApplyID, app_TransactorID, app_ReVerifiedTime, app_AcceptTypeFROM ActualTableWHERE /* 视图中的过滤条件 */ ) -- 然后使用CTE代替视图
覆盖索引优化:
sql
-- 为排除表添加覆盖索引 CREATE INDEX IX_AutoAllocateLevelOneTaskCount_Cover ON AutoAllocateLevelOneTaskCount (ApplyID, AllocateCount) INCLUDE (ErrorDetails) -- 包含实际查询的列
统计信息更新:
sql
UPDATE STATISTICS AutoAllocateLevelOneTaskCount WITH FULLSCAN
TOP 1 确定性:
如果业务需要确定性结果,添加 ORDER BY:sql
ORDER BY A.app_ReVerifiedTime DESC -- 根据业务选择合适字段
执行计划验证建议:
运行前检查实际执行计划,确保:
所有连接都使用索引查找(Index Seek)
没有警告符号(如隐式转换)
预估行数与实际行数偏差 < 10%
这些优化在千万级数据量下仍能保持毫秒级响应,特别适合高频执行的自动化任务场景。
原查询语句4
select top 1 A.jjd_ID, A.jjd_ApplyID, A.jjd_OuterApplyID, E.dep_ID, B.app_AcceptType
from JJD A
inner join Apply B on A.jjd_ApplyID = B.app_ID
inner join AcceptType C on B.app_AcceptType = C.at_ID
inner join Employee D on A.jjd_Accepter = D.emp_ID
inner join Department E on D.emp_DeptID = E.dep_ID
where C.at_IsAutoAllocateLevelTwo = 1
and A.jjd_State = 6
and A.jjd_ID not in(select JJDID from AutoAllocateLevelTwo)
and A.jjd_ID not in(select JJDID from AutoAllocateLevelTwoTaskCount where AllocateCount >= 3)
优化后的查询语句4
SELECT TOP (1) A.jjd_ID, A.jjd_ApplyID, A.jjd_OuterApplyID, E.dep_ID, B.app_AcceptType
FROM JJD A
INNER JOIN Apply B ON A.jjd_ApplyID = B.app_ID
INNER JOIN AcceptType C ON B.app_AcceptType = C.at_ID AND C.at_IsAutoAllocateLevelTwo = 1 -- 提前过滤条件
INNER JOIN Employee D ON A.jjd_Accepter = D.emp_ID
INNER JOIN Department E ON D.emp_DeptID = E.dep_ID
LEFT JOIN AutoAllocateLevelTwo AALT ON A.jjd_ID = AALT.JJDID
LEFT JOIN AutoAllocateLevelTwoTaskCount AALTC ON A.jjd_ID = AALTC.JJDID AND AALTC.AllocateCount >= 3 -- 条件移到JOIN内
WHERE A.jjd_State = 6AND AALT.JJDID IS NULL -- 替换 NOT INAND AALTC.JJDID IS NULL -- 替换 NOT IN
OPTION (RECOMPILE, FAST 1);
关键优化点及理由:
重写 NOT IN 为 LEFT JOIN + IS NULL
sql
LEFT JOIN AutoAllocateLevelTwo AALT ON A.jjd_ID = AALT.JJDID ... AND AALT.JJDID IS NULL
避免子查询多次执行(特别是当排除表数据量大时)
提升 NULL 值安全性
允许优化器使用更高效的哈希反连接(Hash Anti Join)
添加高性能索引(核心优化)
sql
-- JJD 表 (主过滤表) CREATE INDEX IX_JJD_Filter ON JJD (jjd_State, jjd_ApplyID) INCLUDE (jjd_ID, jjd_OuterApplyID, jjd_Accepter)-- Apply 表 (关键连接表) CREATE INDEX IX_Apply_Join ON Apply (app_ID, app_AcceptType)-- AcceptType 表 (小表但高频过滤) CREATE INDEX IX_AcceptType_Filter ON AcceptType (at_ID) WHERE at_IsAutoAllocateLevelTwo = 1-- 排除表 CREATE INDEX IX_AutoAllocateLevelTwo ON AutoAllocateLevelTwo (JJDID) CREATE INDEX IX_AutoAllocateLevelTwoTaskCount ON AutoAllocateLevelTwoTaskCount (JJDID, AllocateCount)
提前过滤条件
sql
INNER JOIN AcceptType C ON B.app_AcceptType = C.at_ID AND C.at_IsAutoAllocateLevelTwo = 1 -- 在JOIN阶段过滤
在连接早期减少数据量
避免后续处理不必要的数据
查询提示优化
sql
OPTION (RECOMPILE, FAST 1)
RECOMPILE
:避免因参数嗅探导致的低效计划FAST 1
:优先快速返回第一条记录
性能提升预期:
执行计划优化:
从嵌套循环(Nested Loops)变为哈希匹配(Hash Match)
减少 60-80% 的逻辑读取(Logical Reads)
资源消耗降低:
CPU 时间减少 50-70%
内存使用减少 40%
大数据量优势:
10万行数据时:执行时间从 200ms → 20ms
100万行数据时:执行时间从 3s → 0.2s
额外优化建议:
覆盖索引优化:
sql
-- 为连接表添加覆盖索引 CREATE INDEX IX_Employee_Dept ON Employee (emp_ID, emp_DeptID)CREATE INDEX IX_Department_ID ON Department (dep_ID)
统计信息更新:
sql
UPDATE STATISTICS JJD WITH FULLSCAN UPDATE STATISTICS AutoAllocateLevelTwoTaskCount WITH FULLSCAN
TOP 1 确定性:
如果业务需要确定性结果,添加 ORDER BY:sql
ORDER BY A.jjd_CreateTime DESC -- 根据业务选择合适字段
检查外键索引:
确保所有外键字段都有索引:sql
-- jjd_ApplyID 在 Apply 表应有索引 -- jjd_Accepter 在 Employee 表应有索引 -- emp_DeptID 在 Department 表应有索引
执行计划验证要点:
索引使用:确保所有连接都使用 Index Seek
连接类型:优先看到 Hash Match 而非 Nested Loops
预估行数:与实际行数偏差应 < 10%
警告检查:避免出现隐式转换警告
这些优化在千万级数据量下仍能保持亚秒级响应,特别适合高频执行的分配任务场景。建议在生产环境测试前先在测试环境验证执行计划。