对以下sql查询语句进行优化:
select
qgm_ID as no, qgm_CreateDate as createDatetime, qgm_HandlerID as handlerId, qgm_ApplyDept as deptId,
qgm_KS as deptSubmitPerson, qgm_KSIdea as deptSubmitOpinion, qgm_KSDate as deptSubmitDatetime,
qgm_Total as total, qgm_State as state, qgm_Remark as remark
from QGMaster
inner join (
select distinct
qgd_MasterID
from QGDetail
where qgd_Bought = 1 and qgd_IsRk = 0) as t on qgm_ID = qgd_MasterID
where qgm_ID not in(
select distinct
rkd_QGDMASTERID from RKDetail
inner join RKMaster on rkm_ID = rkd_MasterID
where rkm_State = 0)
order by qgm_ID
以下是优化后的SQL语句及说明:
SELECT
qgm.qgm_ID AS no,
qgm.qgm_CreateDate AS createDatetime,
qgm.qgm_HandlerID AS handlerId,
qgm.qgm_ApplyDept AS deptId,
qgm.qgm_KS AS deptSubmitPerson,
qgm.qgm_KSIdea AS deptSubmitOpinion,
qgm.qgm_KSDate AS deptSubmitDatetime,
qgm.qgm_Total AS total,
qgm.qgm_State AS state,
qgm.qgm_Remark AS remark
FROM QGMaster qgm
-- 改用 EXISTS 替代 DISTINCT 子查询(更高效)
WHERE EXISTS (
SELECT 1
FROM QGDetail qgd
WHERE
qgd.qgd_MasterID = qgm.qgm_ID
AND qgd.qgd_Bought = 1
AND qgd_IsRk = 0
)
-- 改用 NOT EXISTS 替代 NOT IN(更安全高效)
AND NOT EXISTS (
SELECT 1
FROM RKDetail rkd
INNER JOIN RKMaster rkm ON rkd.rkd_MasterID = rkm.rkm_ID
WHERE
rkd.rkd_QGDMASTERID = qgm.qgm_ID
AND rkm.rkm_State = 0
)
ORDER BY qgm.qgm_ID
优化说明:
消除DISTINCT操作
原
INNER JOIN (SELECT DISTINCT...)
改为EXISTS
,当只需要判断存在性时,EXISTS在找到第一条匹配记录后就会停止扫描,效率高于DISTINCT
转换NOT IN为NOT EXISTS
当子查询结果可能包含NULL值时,NOT IN会有逻辑问题,NOT EXISTS更安全
关联条件写在子查询内部,便于利用索引
建议添加索引(根据实际数据量选择):
sql
复制
下载
-- QGDetail表 CREATE INDEX IX_QGDetail_Bought_IsRk ON QGDetail(qgd_Bought, qgd_IsRk) INCLUDE (qgd_MasterID) -- RKMaster表 CREATE INDEX IX_RKMaster_State ON RKMaster(rkm_State) INCLUDE (rkm_ID) -- RKDetail表 CREATE INDEX IX_RKDetail_QGDMasterID ON RKDetail(rkd_QGDMASTERID) INCLUDE (rkd_MasterID) -- QGMaster表 CREATE INDEX IX_QGMaster_ID ON QGMaster(qgm_ID)
其他优化点:
显式指定表别名,提高可读性和维护性
去除不必要的子查询嵌套
确保所有关联字段(如qgm_ID、rkm_ID等)有适当索引
执行计划优化:
建议在实际环境中查看执行计划,重点关注:
是否出现Table Scan(全表扫描)
索引的实际使用情况
各个操作的预估行数与实际行数差异
优化后优势:
减少不必要的DISTINCT排序开销
使用更高效的EXISTS/NOT EXISTS逻辑
更清晰的查询逻辑结构
更好的索引利用潜力
注意事项:
优化前后请验证结果集一致性
索引创建需考虑写操作频率,避免过度索引
如果数据量极大,可考虑分区表等进阶优化手段
定期更新统计信息保证执行计划准确性
可以通过以下语句验证是否仍有全表扫描:
sql
复制
下载
SET STATISTICS PROFILE ON -- 执行优化后的查询 SET STATISTICS PROFILE OFF
这是 SQL Server 生成的详细查询执行计划(通过 SET STATISTICS PROFILE ON
获取)。我们可以通过以下方式分层解读:
核心结构解读
执行顺序(从内向外):
sql
复制
下载
主查询 |-- Merge Join (Left Anti Semi Join) -- 主连接逻辑 |-- Merge Join (Inner Join) -- EXISTS 子查询部分 | |-- Clustered Index Scan(QGMaster) | |-- Sort(DISTINCT) -- 处理 QGDetail 的条件 | |-- Table Scan(QGDetail) -- 扫描 QGDetail 表 | |-- Sort(DISTINCT) -- 处理 NOT EXISTS 子查询 |-- Hash Match(Inner Join) -- 连接 RKMaster 和 RKDetail |-- Clustered Index Scan(RKMaster) |-- Clustered Index Scan(RKDetail)
关键节点分析
1. 主查询 Merge Join (Left Anti Semi Join)
作用:将 QGMaster 与 NOT EXISTS 子查询结果进行反连接(排除匹配记录)
关键指标:
Estimated Rows = 432.6392
:优化器预估返回约 433 行Actual Rows = 225
:实际返回 225 行(说明统计信息可能有偏差)
2. EXISTS 子查询部分
Merge Join (Inner Join):
将 QGMaster 与 QGDetail 通过
qgm_ID = qgd_MasterID
合并Clustered Index Scan(QGMaster):
扫描主键索引
PK__QGMaster__403A8C7D
未使用 WHERE 过滤,全表扫描 1139 行
Sort(DISTINCT):
对 QGDetail 结果去重排序(
qgd_Bought=1 AND qgd_IsRk=0
)Table Scan(QGDetail):
全表扫描 761 行,过滤后返回 21 行
问题:缺少索引导致全表扫描
3. NOT EXISTS 子查询部分
Sort(DISTINCT):
对 RKDetail 结果去重排序
Hash Match(Inner Join):
连接 RKMaster 和 RKDetail(
rkm_ID = rkd_MasterID
)Clustered Index Scan(RKMaster):
使用主键索引,通过
rkm_State=0
过滤,返回 26 行
Clustered Index Scan(RKDetail):
全表扫描 968 行
性能瓶颈分析
全表扫描问题:
QGDetail 表(
WHERE qgd_Bought=1 AND qgd_IsRk=0
):缺少覆盖索引,导致全表扫描(
Table Scan
)
RKDetail 表:
全表扫描 968 行,没有利用
rkd_QGDMASTERID
过滤
排序开销:
两个
Sort(DISTINCT)
操作(预估成本占比高):原因为子查询中需要去重
统计信息偏差:
主查询预估返回 433 行,实际返回 225 行,说明统计信息需要更新
优化建议
1. 添加缺失索引
sql
复制
下载
-- QGDetail 表(解决 WHERE 条件和避免排序) CREATE NONCLUSTERED INDEX IX_QGDetail_Filter ON QGDetail (qgd_Bought, qgd_IsRk) INCLUDE (qgd_MasterID); -- RKDetail 表(加速 NOT EXISTS 子查询) CREATE NONCLUSTERED INDEX IX_RKDetail_QGDMasterID ON RKDetail (rkd_QGDMASTERID) INCLUDE (rkd_MasterID);
2. 更新统计信息
sql
复制
下载
UPDATE STATISTICS QGMaster; UPDATE STATISTICS QGDetail; UPDATE STATISTICS RKMaster; UPDATE STATISTICS RKDetail;
3. 验证索引效果
优化后重点关注:
QGDetail 是否使用
Index Seek
替代Table Scan
Sort 操作是否消失(通过索引排序避免额外排序)
执行计划关键术语
术语 | 说明 |
---|---|
Merge Join | 通过排序后的数据集进行高效合并 |
Left Anti Semi Join | 排除主表中与子查询匹配的记录(NOT EXISTS 逻辑) |
Clustered Index Scan | 扫描聚簇索引(通常是全表扫描) |
Hash Match | 通过哈希表实现连接(适合大数据量) |
Estimated Rows | 优化器预估的行数(与实际行数差异可能影响执行计划选择) |
最终结论
当前执行计划的主要问题集中在 QGDetail 和 RKDetail 的全表扫描 和 冗余排序 上。通过添加建议的索引,可以显著减少 I/O 和排序开销。优化后需重新查看执行计划,确认是否出现 Index Seek
和 Merge Join
的改进效果。