在分布式數(shù)據(jù)庫(kù)中辈赋,關(guān)聯(lián)查詢不可避免地對(duì)分布在不同節(jié)點(diǎn)的表、分區(qū)進(jìn)行跨節(jié)點(diǎn)的關(guān)聯(lián)操作,數(shù)據(jù)在不同節(jié)點(diǎn)間的分發(fā)方式對(duì)關(guān)聯(lián)查詢的性能至關(guān)重要。在 NLJ(nested-loop join) 和 SPF(subplan filter) 中设褐,需要針對(duì)驅(qū)動(dòng)表中的每一行重新掃描被驅(qū)動(dòng)表中的數(shù)據(jù),如果被驅(qū)動(dòng)表是分區(qū)表且分布在不同的機(jī)器上, 在分布式重新掃描(rescan)的過(guò)程中會(huì)包括資源釋放泣刹、調(diào)度重啟等過(guò)程助析,這些操作常常伴隨著消息等待、同步以及網(wǎng)絡(luò)傳輸操作, 影響執(zhí)行效率椅您。
- 分布式交互包括釋放上一次子協(xié)調(diào)節(jié)點(diǎn)執(zhí)行資源的控制消息外冀、調(diào)度子協(xié)調(diào)節(jié)點(diǎn)的控制消息等,這些若干控制消息的 RPC 交互會(huì)帶來(lái)額外的開(kāi)銷(xiāo)
- 調(diào)度重啟的代價(jià)高是由于 Nested Loop Join 被驅(qū)動(dòng)表可能是分布式復(fù)雜子計(jì)劃掀泳,需要重新建立子計(jì)劃調(diào)度關(guān)系锥惋,這個(gè)重新建立的過(guò)程會(huì)帶來(lái)調(diào)度的開(kāi)銷(xiāo)
- 另外大量的網(wǎng)絡(luò)交互也會(huì)極大的增加響應(yīng)時(shí)間
下面通過(guò)一個(gè)案例分享在 OceanBase 中利用 batch join 優(yōu)化分布式中的 rescan 性能問(wèn)題。
問(wèn)題描述
這個(gè)問(wèn)題出現(xiàn)在一個(gè)測(cè)試環(huán)境的 4.2.1 BP3 版本中开伏,表 a 驅(qū)動(dòng)表 f 做 nested-loop join,a遭商、f 表不在同一個(gè) observer 并且都是非分區(qū)表固灵。驅(qū)動(dòng)表輸出結(jié)果5萬(wàn)行左右,被驅(qū)動(dòng)表 f 走高效索引劫流,最終結(jié)果輸出 11萬(wàn)行左右巫玻,這個(gè)SQL執(zhí)行耗時(shí)要 60秒:
select a.ah,f.aj
from db1.t1 a
join db2.t2 f on a.ah=f.ah
where a.sxrq>=date'2020-01-01'
and aydm in (3029,6209,6210,6365,6366,6469,8179,8180,8271,8294,8789)
and ajxzdm='2'
and jafsmc like '%脫敏%';
在本案例中,對(duì)被驅(qū)動(dòng)表做了5萬(wàn)次rescan祠汇,產(chǎn)生了5萬(wàn)次 rpc 訪問(wèn)仍秤,網(wǎng)絡(luò)上兩個(gè) observer 之間一次 rtt 平均 0.8ms,因此非常慢可很。OceanBase 優(yōu)化器是支持 batch join 的诗力,會(huì)減少 rescan 次數(shù)提升效率,但這里似乎沒(méi)有我抠,下文基于此進(jìn)行解析苇本。
分析過(guò)程
1. 分析 gv$ob_sql_audit
主要目的是獲取兩個(gè)信息:trace_id袜茧、執(zhí)行耗時(shí) 61秒
+----------------------------+----------------+-----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+--------------+--------------+
| usec_to_time(request_time) | svr_ip | plan_type | query_sql | trace_id | elapsed_time | execute_time |
+----------------------------+----------------+-----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+--------------+--------------+
| 2024-03-26 16:04:53.124344 | xxx | 1 | select /*+ monitor */
a.ah
from db1.t1 a
join db2.t2 f on a.ah=f.ah
where a.sxrq>=date'2020-01-01'
and aydm in (3029,6209,6210,6365,6366,6469,8179,8180,8271,8294,8789)
and ajxzdm='2'
and jafsmc like '%脫敏%'; | YB4289116C6E-000613F0D09FF63D-0-0 | 61845207 | 61782771 |
+----------------------------+----------------+-----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------+--------------+--------------+
1 row in set (1.564 sec)
2. 分析執(zhí)行計(jì)劃
從執(zhí)行計(jì)劃來(lái)看,NESTED-LOOP JOIN 算子中被驅(qū)動(dòng)表走了非常高效的索引瓣窄,每次查詢只掃1行笛厦,要挑毛病只有驅(qū)動(dòng)表 A 沒(méi)走索引了、以及沒(méi)使用 batch join 了(use_batch=false):3. 分析gv$sql_plan_monitor
執(zhí)行計(jì)劃看著沒(méi)問(wèn)題俺夕,這個(gè) SQL 不應(yīng)該這么慢裳凸,因此需要通過(guò) gv$sql_plan_monitor
看下真實(shí)的執(zhí)行情況,查詢命令如下:
SELECT op_id, op, output_rows, rescan, threads,
close_time - open_time AS open_dt,
last_row_eof_time - first_row_time AS row_dt,
open_time, close_time, first_row_time, last_row_eof_time
FROM (
SELECT plan_line_id AS op_id,
concat(lpad(' ', max(plan_depth), ' '), plan_operation) AS op, --算子名稱
sum(output_rows) AS output_rows, --算子輸出行數(shù)
sum(STARTS) AS rescan, --算子被 rescan 的次數(shù)
min(first_refresh_time) AS open_time, --算子開(kāi)始(監(jiān)控)時(shí)間
max(last_refresh_time) AS close_time, --算子結(jié)束(監(jiān)控)時(shí)間
min(first_change_time) AS first_row_time, --算子吐出首行數(shù)據(jù)時(shí)間
max(last_change_time) AS last_row_eof_time, --算子吐出最后一行數(shù)據(jù)時(shí)間
count(1) AS threads --線程數(shù)量
FROM gv$sql_plan_monitor
WHERE trace_id = '替換成trace_id'
GROUP BY plan_line_id, plan_operation, plan_depth
ORDER BY plan_line_id
) a;
結(jié)果如下:這里可以看出幾個(gè)信息:
- 驅(qū)動(dòng)表輸出 49557 行劝贸,近5萬(wàn)行姨谷,等于被驅(qū)動(dòng)表 rescan 次數(shù),被驅(qū)動(dòng)表輸出 11萬(wàn)行左右悬荣;
- 每個(gè)算子的耗時(shí)都是 61 秒菠秒,這是正常的,因?yàn)檫@里只做了 nested-loop join氯迂,過(guò)程是:
- 驅(qū)動(dòng)表取一行數(shù)據(jù)践叠,開(kāi)始計(jì)時(shí),這是1號(hào)算子的開(kāi)始吐行時(shí)間
- 到被驅(qū)動(dòng)表進(jìn)行查詢嚼蚀,開(kāi)始計(jì)時(shí)禁灼,這是2號(hào)算子的開(kāi)始吐行時(shí)間
- 直到循環(huán)結(jié)束,1轿曙、2號(hào)算子的最后吐行時(shí)間是一樣的
因此并不能簡(jiǎn)單的認(rèn)為1號(hào)算子即驅(qū)動(dòng)表就耗時(shí)了 61 秒弄捕,這是不對(duì)的。需要從 5萬(wàn)次 rescan 入手导帝。
4. 確認(rèn) rpc_count 和表位置
通過(guò) GV$CDB_OB_TABLE_LOCATIONS
確認(rèn)了 db1.t1守谓、db2.t2 兩張表不在一個(gè) unit 上,因此 join 時(shí)需要跨 observer您单,通過(guò) gv$ob_sql_audit
確認(rèn)這個(gè)SQL執(zhí)行時(shí)產(chǎn)生了49557 次 rpc(rpc_count=49557)斋荞。
對(duì)應(yīng)的執(zhí)行計(jì)劃中,join 算子 use_batch=false虐秦,同樣說(shuō)明被驅(qū)動(dòng)表 f 進(jìn)行了 49557 次 rescan平酿。
5. 開(kāi)啟 batch join
要減少被驅(qū)動(dòng)表的 rescan 次數(shù),需要讓 batch join 生效悦陋, 這由租戶的隱藏變量 _NLJ_BATCHING_ENABLED
來(lái)控制蜈彼, 通過(guò) CDB_OB_SYS_VARIABLES
可以查詢隱藏變量的設(shè)置情況:
select TENANT_ID,NAME,VALUE,INFO,DEFAULT_VALUE from CDB_OB_SYS_VARIABLES where name='_NLJ_BATCHING_ENABLED';
結(jié)果如下,發(fā)現(xiàn) batch join 默認(rèn)關(guān)閉了:將 batch join 開(kāi)啟:SET global _NLJ_BATCHING_ENABLED=true;
俺驶,然后再執(zhí)行SQL幸逆,耗時(shí)從61秒下降到 2.8秒,執(zhí)行計(jì)劃中 use_batch=true。
總結(jié)
本案例中秉颗,SQL執(zhí)行慢的原因是被驅(qū)動(dòng)表與驅(qū)動(dòng)表不在一個(gè) observer 上痢毒,當(dāng)前使用版本 4.2.1 BP3,_NLJ_BATCHING_ENABLED
默認(rèn)關(guān)閉蚕甥,因此沒(méi)有使用 DAS Group rescan 優(yōu)化哪替,即沒(méi)有使用 batch join,導(dǎo)致被驅(qū)動(dòng)表 rescan 次數(shù)太多菇怀,并且是跨 observer rescan凭舶,所以非常慢。
本案例中有幾個(gè)注意事項(xiàng):
- gv$sql_plan_monitor 中的 rescan 不能代表真實(shí)的 rescan 次數(shù)爱沟,應(yīng)該恒等于驅(qū)動(dòng)表的輸出行數(shù)帅霜,即使優(yōu)化器進(jìn)行了 DAS Group rescan 優(yōu)化
- gv$ob_sql_audit 中 rpc_count 字段可以用來(lái)確認(rèn) NLJ 算子中被驅(qū)動(dòng)表的 rescan 次數(shù)
- gv$sql_plan_monitor 中的查詢出來(lái)的算子輸出行的總時(shí)間結(jié)果有可能多個(gè)算子一樣,這個(gè)要看具體算子的執(zhí)行邏輯來(lái)解讀
-
batch join 并不像官方文檔說(shuō)的一樣總是默認(rèn)開(kāi)啟的呼伸,后來(lái)在 release note 中找到了原因:因?yàn)橐恍?bug 在V4.2.1 BP2 Hotfix1 版本中身冀,默認(rèn)關(guān)閉了該優(yōu)化功能,后面又在V4.2.1 BP5 默認(rèn)打開(kāi)該優(yōu)化開(kāi)關(guān)括享。