云数据库 PostgreSQL 支持启发式优化器插件 tencentdb_heuristic,本文为您介绍关于 tencentdb_heuristic 的说明。
功能概述
enable_nl2hj:当外层小、内层大且外层无 Param 关联时,惩罚 NestLoop、让 HashJoin 胜出。
enable_index:在满足 SAME / UNIQUE / INCLUDE 等子条件时偏置索引路径(可与 enable_seqscan 一同调节)。
enable_danger_joinkeys:对配置为“danger”的连接列,对 MergeJoin/HashJoin 施加可调惩罚,倾向于回到 NestLoop 或换列。
前提条件
PostgreSQL 18,版本 ≥ v18.4_r1.10。
使用限制
该插件需要通过 shared_preload_libraries 或 LOAD 加载生效。
所有规则均为成本调整 / 候选路径过滤,不改变查询结果集,仅改变 EXPLAIN 形状与执行时间。
主开关 tencentdb_heuristic.enable 关闭时,所有子规则一律失效。
创建插件
检查 shared_preload_libraries 已包含 tencentdb_heuristic:
postgres=> SHOW shared_preload_libraries;
创建扩展:
CREATE EXTENSION tencentdb_heuristic;SELECT * FROM pg_extension WHERE extname = 'tencentdb_heuristic';
会话级临时加载(仅当前 backend 生效):
LOAD 'tencentdb_heuristic';
参数总览
主开关与子开关
参数 | 类型 | 默认 | 说明 |
tencentdb_heuristic.enable | bool | off | 插件总开关,关闭则所有子规则失效。 |
tencentdb_heuristic.enable_index | bool | off | 开启索引路径启发式偏置。 |
tencentdb_heuristic.enable_seqscan | bool | on | 是否参与 seqscan 与 index 的对比,配合 enable_index 使用。 |
tencentdb_heuristic.enable_nl2hj | bool | off | 开启 NestLoop→HashJoin 惩罚规则。 |
tencentdb_heuristic.enable_danger_joinkeys | bool | off | 对危险 join key 上的 HashJoin/MergeJoin 施加惩罚。 |
数值调参
参数 | 类型 | 默认 | 说明 |
tencentdb_heuristic.nl2hj_startup | int | 10 | 外层行数低于该阈值时优先保留 NL;反之考虑改写为 HJ。 |
tencentdb_heuristic.nl2hj_magnify | int | 10 | NL→HJ 规则的成本放大因子。 |
tencentdb_heuristic.nl2hj_sameouter_nl_inner_rows_startup | int | 50 | sameouter-NL 保护:内层行低于该阈值不改写。 |
tencentdb_heuristic.nl2hj_sameouter_hj_inner_rows_shutdown | int | 500000 | sameouter-HJ 保护:内层行高于该阈值不改写。 |
tencentdb_heuristic.joinkeys_magnify | real | 1.0 | danger join-key 惩罚系数:< 100为乘因子,≥ 100为附加成本。 |
字符串子选项
参数 | 类型 | 默认 | 说明 |
tencentdb_heuristic.danger_joinkeys | text | '' | danger join keys 列表,冒号分隔,形如 public.orders(uid,gid):public.users(id)。 |
tencentdb_heuristic.index_options | text | '' | index 规则子选项,逗号分隔(same,unique,include,restrict,funcs,udf,const,cost,startupcost,param,debug 等)。 |
tencentdb_heuristic.nl2hj_options | text | '' | NL2HJ 子选项(samechild,sameouter_withparam,outer_not_scan,pathkey,precheck,violence,cost,debug 等)。 |
使用示例
danger_joinkeys:让 MergeJoin/HashJoin 退回 NestLoop
当业务判定某些连接列(例如低基数、倾斜严重的 uid)不宜走哈希 / 排序,可将其加入 danger 列表。
先准备两张演示表(orders.uid 故意做成低基数、重度倾斜):
CREATE TABLE public.users (id int PRIMARY KEY, name text);CREATE TABLE public.orders (id int PRIMARY KEY, uid int NOT NULL, amount numeric);INSERT INTO public.usersSELECT i, 'u' || i FROM generate_series(1, 100) i;-- 100 万行订单,但 uid 只有 10 个不同值,且 90% 集中在 uid=1INSERT INTO public.ordersSELECT i,CASE WHEN random() < 0.9 THEN 1ELSE (random() * 9)::int + 2END,(random() * 1000)::numeric(10,2)FROM generate_series(1, 1000000) i;ANALYZE public.users, public.orders;
然后打开 danger:
LOAD 'tencentdb_heuristic';SET tencentdb_heuristic.enable = on;SET tencentdb_heuristic.enable_danger_joinkeys = on;SET tencentdb_heuristic.danger_joinkeys = 'public.orders(uid)';SET tencentdb_heuristic.joinkeys_magnify = 1000; -- 强惩罚EXPLAIN (COSTS OFF)SELECT * FROM public.orders o JOIN public.users u ON o.uid = u.id;QUERY PLAN---------------------------------Hash JoinHash Cond: (o.uid = u.id)-> Seq Scan on orders o-> Hash-> Seq Scan on users u(5 rows)
原本的 MergeJoin/HashJoin 会因 danger 列的额外成本被打压,从而落到 NestLoop。
关闭调试打印可用 SET tencentdb_heuristic.enable_danger_joinkeys_debug = on。
nl2hj:把 NestLoop 改写为 HashJoin
外层行少、内层行多且无参数化时,NestLoop 反而更慢。
先准备两张体积悬殊的演示表(外层5行、内层100万行,等值 join 列均匀分布):
CREATE TABLE small_outer (k int PRIMARY KEY, tag text);CREATE TABLE big_inner (k int NOT NULL, payload text);INSERT INTO small_outerSELECT i, 't' || i FROM generate_series(1, 5) i;INSERT INTO big_innerSELECT (random() * 5)::int + 1, md5(i::text)FROM generate_series(1, 1000000) i;CREATE INDEX ON big_inner(k); -- 有索引才能让 mainline 先选出 NL,随后被规则改写ANALYZE small_outer, big_inner;
然后打开 nl2hj:
SET tencentdb_heuristic.enable = on;SET tencentdb_heuristic.enable_nl2hj = on;SET tencentdb_heuristic.nl2hj_startup = 10; -- 外层 < 10 行才保留 NLSET tencentdb_heuristic.nl2hj_magnify = 50;SET tencentdb_heuristic.nl2hj_options = 'cost,violence,precheck';EXPLAIN (COSTS OFF)SELECT * FROM small_outer o, big_inner b WHERE o.k = b.k;QUERY PLAN---------------------------------------Hash JoinHash Cond: (b.k = o.k)-> Seq Scan on big_inner b-> Hash-> Seq Scan on small_outer o(5 rows)
NL 路径的成本被放大50倍,HashJoin 胜出。
内层无索引可用的 NL 路径成本乘100,倾向切换到 HashJoin / MergeJoin。
enable_index:偏置索引路径选择
SET tencentdb_heuristic.enable = on;SET tencentdb_heuristic.enable_index = on;SET tencentdb_heuristic.enable_seqscan = on;SET tencentdb_heuristic.index_options = 'same,unique,include,cost';
index_options 中的每个子选项分别控制:
same:命中相同列表达式时优先索引。
unique:命中唯一约束/唯一索引时优先。
include:INCLUDE 列可覆盖查询时优先。
cost:打开索引-顺扫成本对比调整。
debug:打印诊断信息。