数据库-GaussDB-基础篇-典型SQL调优点-多表JOIN案例
- 某业务慢SQL示例:
explain analyse SELECT
a.gl_sr,
a.bs_st,
a.id_chk_no,
a.tt_tr_no,
a.bs_tl_no,
a.tt_it_no,
a.tt_dt,
count(a.gl_sr) countleng,
count(a.gl_sr) busleng,
a.im,
a.lsuid,
a.fby,
a.fbt,
a.svglsn,
a.svt,
b.ctn_amt,
b.ctn_tel,
b.daacnt,
b.mc,
d.note1,
d.idchno,
substring(d.elco,1,3) as ebservcode
from ix_log_busi_log a
LEFT JOIN ix_log_ele_log b ON a.gl_sr = b.gl_sr
LEFT JOIN ix_net_ins_appr_info info on a.id_chk_no=info.tsct_srlno
LEFT JOIN ix_log_cust_log c on a.svglsn = c.svglsn
LEFT JOIN ix_upm_men_info d on a.mc=d.mc
LEFT JOIN ix_log_com_log ii on a.gl_sr=ii.gl_sr
where a.tt_it_no='803102' and a.tt_tr_no='100857'
and a.tt_dt >= '20250801' and a.tt_dt <= '20260310' and a.im='Y'
group by a.gl_sr,a.bs_st,a.id_chk_no,a.tt_tr_no,a.bs_tl_no,a.tt_it_no,a.tt_dt,a.im,a.lsuid,a.fby,a.fbt,a.svglsn,a.svt,b.ctn_amt,b.ctn_tel,b.daacnt,b.mc,d.note1,d.idchno,substring(d.elco,1,3)
order by a.gl_sr;
表关联关系
主表 ix_log_busi_log a LEFT JOIN 5张表 ix_log_ele_log b ix_net_ins_appr_info info ix_log_cust_log cix_upm_men_info d ix_log_com_log ii
SQL关联顺序:((( (a LEFT JOIN b) LEFT JOIN info ) LEFT JOIN c ) INNER JOIN d ) LEFT JOIN ii
完整执行计划树(缩进代表父子层级,id 为算子编号)
1 -> Streaming (type: GATHER)
2 -> Sort
3 -> HashAggregate
4 -> Nested Loop Left Join (5, 23)
5 -> Hash Right Join (6, 8)
6 -> Streaming(REDISTRIBUTE)
7 -> Seq Scan on ib_log_comm_log ii
8 -> Hash
9 -> Hash Right Join (10,12)
10 -> Streaming(REDISTRIBUTE)
11 -> Seq Scan on te_log_element_log b
12 -> Hash
13 -> Streaming(REDISTRIBUTE)
14 -> Nested Loop Left Join (15,22)
15 -> Streaming(PART REDISTRIBUTE PART ROUNDROBIN)
16 -> Hash Right Join (17,19)
17 -> Streaming(REDISTRIBUTE)
18 -> Seq Scan on te_network_inspect_approve_info info
19 -> Hash
20 -> Streaming(PART REDISTRIBUTE PART ROUNDROBIN)
21 -> Index Scan using idx_ib_log_business_log_index1 on ib_log_business_log a
22 -> Index Scan using pk_ib_log_custser_log on ib_log_custser_log c
23 -> Index Scan using gbl_menunum_idx on ib_upm_menu_info d
- Predicate Information 谓词与 JOIN 条件:
5 -- Hash Right Join (6, 8)
Hash Cond: ((ii.gl_sr)::text = (a.gl_sr)::text)
9 -- Hash Right Join (10, 12)
Hash Cond: ((b.gl_sr)::text = (a.gl_sr)::text)
Skew Join Optimized by Statistic
16 -- Hash Right Join (17, 19)
Hash Cond: ((info.tsct_srlno)::text = (a.id_chk_no)::text)
Skew Join Optimized by Statistic
21 -- Index Scan using idx_ib_log_business_log_index1 on ib_log_business_log a
Index Cond: ((tt_it_no)::text = '803102'::text) AND ((tt_it_no)::text = '100857'::text)
Filter: ((tt_dt)::text >= '20250801'::text) AND ((tt_dt)::text <= '20260310'::text) AND ((im)::text = 'Y'::text)
22 -- Index Scan using pk_ix_log_cust_log on ix_log_cust_log c
Index Cond: ((a.svglsn)::text = (svglsn)::text)
23 -- Index Scan using gbl_menunum_idx on ib_upm_menu_info d
Index Cond: ((a.mc)::text = (mc)::text)
Memory Information内存使用信息
所有 Hash 算子内存占用极低,哈希表全部驻留内存,无磁盘溢出;Sort 算子内存充足,没有大量磁盘排序,本次查询数据量不大。
Coordinator Query Peak Memory:
Query Peak Memory: 13MB
Datanode:
Max Query Peak Memory: 1MB
Min Query Peak Memory: 1MB
2 -- Sort
Sort Method: quicksort Memory: 25kB ~ 26kB
Sort Method: quicksort Disk: 1024kB ~ 0kB
8 -- Hash
Max Buckets: 32768 Max Batches: 1
Max Memory Usage: 2kB
Min Buckets: 32768 Min Batches: 1
Min Memory Usage: 1kB
12 -- Hash
Max Buckets: 32768 Max Batches: 1
Max Memory Usage: 2kB
Min Buckets: 32768 Min Batches: 1
Min Memory Usage: 1kB
19 -- Hash
Max Buckets: 32768 Max Batches: 1
Max Memory Usage: 2kB
Min Buckets: 32768 Min Batches: 1
Min Memory Usage: 1kB
用户自定义性能剖面
这部分是 CN 协调节点下发任务、建立 DN 连接、序列化执行计划、下发 SQL 命令的开销。整体耗时很小,集群任务下发效率正常。
Plan Node Id: 1 Track name: coordinator get datanode connection
(actual time=[0.090, 0.090], calls=[1, 1])
Plan Node Id: 1 Track name: Coordinator check and update node definition
(actual time=[0.004, 0.004], calls=[1, 1])
Plan Node Id: 1 Track name: Coordinator serialize plan
(actual time=[3.276, 3.276], calls=[1, 1])
Plan Node Id: 1 Track name: Coordinator send query id with sync
(actual time=[1.027, 1.027], calls=[1, 1])
Plan Node Id: 1 Track name: Coordinator send begin command
(actual time=[0.003, 0.003], calls=[1, 1])
Plan Node Id: 1 Track name: Coordinator start transaction and send query
(actual time=[0.218, 0.218], calls=[1, 1])
Plan Node Id: 2 Track name: Datanode start up stream thread
(actual time=[0.292, 0.352], calls=[1, 1])
-
Query Summary查询整体汇总:
-
Planner runtime 203.281ms:6 表关联,优化器枚举多种 JOIN 组合、统计信息代价计算,生成分布式执行计划耗时。
-
Datanode executor run time 约 1029ms:整条 SQL 主要耗时在 DN 端,包含多层 Hash Join、多次跨节点 Streaming 重分布、索引查找。CN 本身只负责调度、最后汇总,计算压力很小。
-
Total runtime:1094.893ms,SQL 从提交到全部结果返回总耗时约 1.09 秒。
Datanode executor start time [dn_6009_6010_6011_6012, dn_6001_6002_6003_6004]: [51.450 ms ,52.551 ms]
Datanode executor run time [dn_6005_6006_6007_6008, dn_6013_6014_6015_6016]: [1029.770 ms,1030.450 ms]
Datanode executor end time [dn_6009_6010_6011_6012, dn_6001_6002_6003_6004]: [0.249 ms,0.370 ms]
Coordinator executor start time: 0.744 ms
Coordinator executor run time: 1094.029 ms
Coordinator executor end time: 0.089 ms
Planner runtime: 203.281 ms
Plan size: 19608 byte
Query Id: 73464969339729871
Total runtime: 1094.893 ms
核心原理与调优理解
-
JOIN 执行顺序
从内向外逐层叠加表,一次只新增一张表:
a left
join info【plan id 16~21】→ 中间集【a+info】A+info left join c【plan id 14~22】→中间集【a+info+c】a+info+c left join b【plan id 9~22】→中间集【a+info+c+b】a+info+c+b left join ii【plan id 5~22】→中间集【a+info+c+b+ii】a+info+c+b+ii left join d【plan id 4~23】→总结果集合【a+info+c+b+ii+d】d 表是最后一步才关联,由最外层 Nested Loop Left Join 驱动。外表每一行,都要去d表做索引 lookup。
-
分布式重分布关键点
每一层 Hash Join 使用的关联键不同:
id_chk_no→gl_sr。每更换 JOIN key,就会触发
Streaming REDISTRIBUTE,跨 DN 网络传输中间结果。多层 JOIN 会叠加网络开销,这是多表大关联 SQL 最主要的性能瓶颈。 -
优势
a表前置索引过滤,提前缩小数据集;c、d 表使用索引扫描,单行 lookup 效率高;所有 Hash Join 哈希表都在内存,无磁盘溢出。
-
潜在风险
最外层 NL 左连接 d 表:如果中间结果行数膨胀,d表索引查找次数会线性增加,性能快速恶化。多表连续关联,多层重分布,数据量变大后网络 IO 会急剧上涨。
-
下推视角总结
内层部分算子可以下推到 DN 本地执行;但因为多层 JOIN 的关联字段不断变化,需要多次重分布,不能全程把所有计算全部留在 DN。分布式调优的核心目标就是减少跨节点重分布次数,尽量让计算在 DN 本地完成,减少 CN 汇聚的数据量。
优化HINT
/*+ set(enable_stream_operator off) set(enable_remote_param on) set(enable_hashjoin off) leading(((a ii))b) */
Hint 说明:
-
set(enable_stream_operator off):关闭流式算子,控制分布式重分布行为 -
set(enable_remote_param on):开启远程参数,支持参数下推到 DN -
set(enable_hashjoin off):关闭 HashJoin,强制走 NestedLoop 嵌套循环 -
leading(((a ii))b):强制指定 JOIN 驱动顺序,固定 A 表作为驱动表,控制表关联顺序,改变优化器自主选择的 join 次序
-> Nested Loop Left Join
-> Nested Loop Left Join
-> Nested Loop Left Join
-> Nested Loop Left Join
-> Nested Loop Left Join
-> Data Node Scan on "ix_log_busi_log" REMOTE_TABLE_QUERY_
-> Data Node Scan on ix_log_com_log REMOTE_TABLE_QUERY_
-> Data Node Scan on ix_log_ele_log REMOTE_TABLE_QUERY_
-> Data Node Scan on ix_net_ins_appr_info REMOTE_TABLE_QUERY_
-> Data Node Scan on ix_log_cust_log REMOTE_TABLE_QUERY_
-> Data Node Scan on ix_upm_men_info REMOTE_TABLE_QUERY_
Total runtime: 77.799 ms
优化前后对比
-
优化前总耗时:1094.893ms
-
Hint 优化后总耗时:77.799ms
性能提升巨大,从 1.09 秒下降到 78ms 左右。
原理解读
-
耗时对比
-
优化前:1094.893ms
-
Hint 优化后:77.799ms
性能提升巨大,从 1.09 秒下降至 78ms 左右。
-
-
优化前执行特点
优化器自主选择多层 HashJoin + 多次
Streaming REDISTRIBUTE跨节点重分布。每更换一次 JOIN Key,就需要在 DN 之间搬迁中间结果,网络传输开销是主要瓶颈。 -
Hint 优化后的原理
-
关闭 HashJoin,全部改为 NestedLoop 嵌套循环;
-
通过
leading强制指定驱动表顺序,A 表作为主驱动,并且把过滤条件下推到 DN 执行(REMOTE_TABLE_QUERY); -
大量查询逻辑下推到 DN 本地执行,减少跨节点数据重分布,大幅减少网络传输的数据量;
-
只将少量最终结果汇总到 CN,降低 CN 汇聚压力。
-
-
Hint 的风险与注意事项
-
Hint 属于人工干预优化器,是应急调优手段,不是永久最优方案。当表数据量、数据分布发生变化时,Hint 可能产生很差的执行计划;
-
多条 leading 写在一起容易出现 hint 冲突,冲突的 hint 会被优化器直接丢弃,写 hint 要精简;
-
NestedLoop 只适合小结果集场景。如果 A 表过滤后返回行数变多,NL 性能会快速恶化,大数据量场景 HashJoin 更加合适。
-
-
分布式下推调优总结
分布式 SQL 调优的核心就是尽量把计算下推到 DN 本地,减少跨 DN 的重分布、减少网络传输,不要把大量中间数据全部汇聚到 CN。
优化器自动生成的计划不一定最优,在业务稳定、数据分布变化小的场景,可以使用 Hint 固定 JOIN 顺序与算子类型;但生产环境使用 Hint 需要持续观察数据变化,防止后续出现性能回退。

浙公网安备 33010602011771号