大表ER_GL查询性能优化及关联SQL语句重构咨询
ER_GL大表查询优化与关联SQL重构
一、全表查询性能优化
表结构与现状
ER_GL表结构:
ER_GL (BUILDID, EYEAR, TTYPE, PID, ICAT, SDATE, LNUM, AMOUNT, ORGID, ETC.)
已建立主键唯一索引:(BUILDID, EYEAR, TTYPE, PID, ICAT, SDATE, LNUM),执行无过滤条件的select * from ER_GL耗时3分钟。
优化方案
全表查询的核心瓶颈是数据量过大导致的IO开销,要做到秒级响应,可从以下方向入手:
- 避免全列扫描:如果业务不需要所有列,绝对不要用
select *,只查询需要的字段。比如只查金额和年份:select EYEAR, AMOUNT from ER_GL,减少磁盘IO和内存占用,速度会大幅提升。 - 分区表改造:按
EYEAR或BUILDID将大表拆分为多个分区,数据库可以并行扫描多个分区,大幅缩短全表查询时间。 - 存储与配置优化:使用SSD存储替代HDD,提升随机读写速度;调整数据库并行查询参数(如SQL Server的
MAXDOP),让查询利用多CPU核心并行处理。 - 索引无效说明:新增非聚集索引对全表
select *没用——非聚集索引要么需要回表查全列,要么做全列覆盖(和主键索引体积一样,完全没必要),反而会增加写入时的索引维护开销。
二、关联查询的问题分析与重构
原语句存在的问题
- 子查询重复扫描:两个关联子查询会对ER_GL表进行N次扫描(N是
#TEMP_CALC分组后的行数),数据量越大,性能下降越明显。 - 语法错误:第一个子查询中误用了别名
Y.TTYPE = 'A',应该是X.TTYPE = 'A'。 - 聚合函数引用错误:子查询中直接引用
MAX(S.ENDDATE),但外层GROUP BY未包含ENDDATE,多数数据库会直接报错,逻辑上也不成立。 - 索引覆盖不足:JOIN条件中的
SDATE BETWEEN ...范围过滤,如果没有对应索引,会导致大量表扫描或回表操作。 - NOLOCK风险:
#TEMP_CALC (NOLOCK)会读取未提交的脏数据,除非业务明确允许,否则不要使用。 - Cross join替代Inner join的误区:这种说法完全错误,Cross join是笛卡尔积,会产生海量冗余数据,性能远不如Inner join,绝对不能用在这个场景。
重构后的SQL语句
WITH S_AGG AS ( -- 预计算临时表的分组聚合,拿到每个分组的MAX(ENDDATE) SELECT S.BUILDID, S.ICAT, S.EYEAR, S.PID, S.EFFDATE, S.EDATE, MAX(S.ENDDATE) AS MAX_ENDDATE FROM #TEMP_CALC S WHERE S.BUILDID = @XBUILDID AND S.ICAT = @XICAT AND S.EYEAR = @XEYEAR GROUP BY S.BUILDID, S.ICAT, S.EYEAR, S.PID, S.EFFDATE, S.EDATE ) SELECT SA.EYEAR, SA.BUILDID, GETDATE(), SUM(G.AMOUNT) AS SUM_CURRENT_YEAR, ISNULL(X_SUM.SUM_AMOUNT, 0) AS SUM_XEYEAR, ISNULL(Y_SUM.SUM_AMOUNT, 0) AS SUM_XEYEAR2, G.ORGID FROM S_AGG SA INNER JOIN ER_GL G ON G.BUILDID = SA.BUILDID AND G.EYEAR = SA.EYEAR AND G.TTYPE = 'A' AND G.PID = SA.PID AND G.ICAT = SA.ICAT AND G.SDATE BETWEEN SA.EDATE AND SA.MAX_ENDDATE -- 预计算两个年份的金额总和,避免重复扫描ER_GL LEFT JOIN ( SELECT PID, SUM(AMOUNT) AS SUM_AMOUNT FROM ER_GL WHERE BUILDID = @XBUILDID AND EYEAR = @XEYEAR AND TTYPE = 'A' AND ICAT = @XICAT GROUP BY PID ) X_SUM ON X_SUM.PID = SA.PID LEFT JOIN ( SELECT PID, SUM(AMOUNT) AS SUM_AMOUNT FROM ER_GL WHERE BUILDID = @XBUILDID AND EYEAR = @XEYEAR2 AND TTYPE = 'A' AND ICAT = @XICAT GROUP BY PID ) Y_SUM ON Y_SUM.PID = SA.PID GROUP BY SA.BUILDID, SA.ICAT, SA.EYEAR, SA.PID, SA.EFFDATE, G.ORGID, X_SUM.SUM_AMOUNT, Y_SUM.SUM_AMOUNT ORDER BY SA.PID, SA.EFFDATE
重构思路与索引建议
- 预聚合减少扫描:用CTE先计算
#TEMP_CALC的分组和MAX(ENDDATE),避免在子查询中重复计算;将关联子查询改为独立聚合查询,ER_GL只被扫描2次(对应两个年份),而非N次。 - 索引优化:给ER_GL创建以下覆盖索引,消除回表操作:
-- 主查询用的覆盖索引 CREATE NONCLUSTERED INDEX IX_ER_GL_MAIN ON ER_GL (BUILDID, EYEAR, TTYPE, PID, ICAT, SDATE) INCLUDE (AMOUNT, ORGID); -- 年份聚合查询用的覆盖索引 CREATE NONCLUSTERED INDEX IX_ER_GL_YEAR_AGG ON ER_GL (BUILDID, EYEAR, TTYPE, ICAT, PID) INCLUDE (AMOUNT);
内容的提问来源于stack exchange,提问作者Sri
相关产品推荐
相关产品推荐

