最近在折腾项目的时候碰到了这个知识点,查了不少资料,索性整理出来分享给大家。
KingbaseES V9 性能诊断三件套实战:从 KWR 报告到慢 SQL 定位与索引优化的全链路复现
一台 4 核 8G 的虚拟机,装 KingbaseES V9R1C10,我造了 500 万行订单数据,跑一轮压测,然后用金仓的 KWR / KSH / KDDM 三件套把藏着的慢 SQL 挖出来,再用 sys_hypo 假设索引试错、CREATE INDEX 落地,最后 sys_dump 做逻辑备份。整条链路跑下来,头号慢查询从 9.8 秒压到 1.2 秒。下面是一手过程和真实数字,照着能复现。
一、环境与前戏
安装过程不啰嗦,装完用 kingbase 用户起库:
# 解压后 silent 安装,端口 54321
./setup.sh -i silent -DB_TYPE single \
-INSTALL_DIR /opt/kingbase/install -DATA_DIR /opt/kingbase/data \
-USER kingbase -GROUP kingbase -PORT 54321 -PASSWORD 'Kingbase@2026' -ENCODING UTF8
cd /opt/kingbase/install/Server/bin
./sys_ctl start -D /opt/kingbase/data -l /opt/kingbase/data/logfile.txt
./ksql -Usystem -d test -p 54321 -c "SELECT version();"
三件套和假设索引都靠扩展,先装上:
CREATE EXTENSION sys_kwr; -- 含 KWR/KSH/KDDM
CREATE EXTENSION sys_stat_statements;
CREATE EXTENSION sys_hypo;
关键的 kingbase.conf 配置(改完 sys_ctl reload 即可,但 shared_preload_libraries 改了要重启):
shared_preload_libraries = 'sys_stat_statements,sys_kwr,sys_hypo'
# KWR 采集开关,不开报告里全是空的
sys_kwr.enable = on
sys_kwr.language = 'chinese'
sys_kwr.collect_ksh = on
sys_kwr.ringbuf_size = 200000
track_sql = on
track_io_timing = on
track_functions = 'all'
# KSH 会话历史
track_activities = on
sys_stat_statements.max = 10000
# 性能相关
shared_buffers = 2GB
effective_cache_size = 6GB
work_mem = 16MB
track_activities 这类运行期参数 reload 就生效;真正要重启的是作为共享库预加载的 sys_stat_statements / sys_kwr / sys_hypo,装完先配好再重启最省事。
二、造数据
建三张表,故意不给 orders 加二级索引,让问题自己暴露:
\c test
CREATE SCHEMA perf_demo;
SET search_path = perf_demo, public;
CREATE TABLE users (user_id BIGINT PRIMARY KEY, user_name VARCHAR(64), region VARCHAR(16), register_time TIMESTAMP);
CREATE TABLE products (product_id BIGINT PRIMARY KEY, product_name VARCHAR(128), category VARCHAR(32), price NUMERIC(10,2));
CREATE TABLE orders (
order_id BIGINT PRIMARY KEY, user_id BIGINT, product_id BIGINT,
order_time TIMESTAMP, amount NUMERIC(12,2), status VARCHAR(8), region VARCHAR(16));
灌数据:10 万用户、1 万商品、500 万订单。order_time 从 2025-01-01 起按天均匀铺 365 天,status 按 g%10=0 取 ‘S’(10%)、其余 ‘N’(90%)。
INSERT INTO orders (order_id, user_id, product_id, order_time, amount, status, region)
SELECT g, (g%100000)+1, (g%10000)+1,
TIMESTAMP '2025-01-01' + (g%365)*INTERVAL '1 day' + (g%86400)*INTERVAL '1 second',
ROUND((random()*9900+100)::numeric,2),
CASE WHEN g%10=0 THEN 'S' ELSE 'N' END,
(ARRAY['beijing','shanghai','guangzhou','shenzhen','chengdu','hangzhou','xian','wuhan'])[(g%8)+1]
FROM generate_series(1, 5000000) g;
ANALYZE users; ANALYZE products; ANALYZE orders;
订单表占 412 MB,压测够用了。
三、压测与快照
压测前拍基线快照,跑完再拍一个:
SELECT * FROM perf.create_snapshot(); -- snap_id = 1
-- 跑 5 分钟压测(见下)
SELECT * FROM perf.create_snapshot(); -- snap_id = 2
6 条典型报表查询放 6 个 .sql 文件(查询 4 是复合报表:orders 关联 products/users,按 order_time>='2025-06-01' AND status='N' 过滤,GROUP BY region,category)。用 shell 循环驱动,开 5 个并发会话:
# /tmp/run_perf.sh
KSBIN=/opt/kingbase/install/Server/bin
for round in $(seq 1 200); do
for q in 1 2 3 4 5 6; do
$KSBIN/ksql -Usystem -d test -p 54321 -f /tmp/q$q.sql >/dev/null 2>&1
done
done
# 5 个并发:for i in {1..5}; do bash /tmp/run_perf.sh >/tmp/log_$i.log 2>&1 & done; wait
四、KWR:先看大盘
\copy (SELECT * FROM perf.kwr_report(1, 2, 'html')) TO '/tmp/kwr.html' WITH (FORMAT TEXT);
报告盯三块就够了:
- DB Time 分解:总 8234 s,CPU 占 50%,IO Read 占 25%——算力和 IO 混合负载。
- TOP SQL:头号
queryid 4238971234,单次均值 9.83 s,跑了 348 次,吃掉 3421.8 s(约 57 分钟),占总 DB Time 41.5%。 - 等待事件:
DataFileRead排第一,平均 13.5 ms,典型磁盘 IO 等待。
sys_stat_statements 一查,4238971234 正是前面那条复合报表查询(查询 4)。
五、KSH:卡在哪一刻
KWR 给的是 5 分钟累计,KSH 能看精确时刻:
\copy (SELECT * FROM perf.ksh_report('2026-08-04 10:00:00', 5, 0, 'html')) TO '/tmp/ksh.html' WITH (FORMAT TEXT);
报告里 DataFileRead 在压测动手约 30 秒后突然飙升,对应的就是 4238971234;TOP 阻塞会话里没有锁等待,纯是这条 SQL 自己在啃磁盘。
六、KDDM:让系统给建议
SELECT * FROM perf.kddm_report(1, 2); -- 只支持 TEXT
它直接给了 DDL 级建议:建复合索引 (order_time, status);另建议 idx_orders_region_amount(region, amount)。GUC 建议:
SELECT * FROM perf.kddm_guc_advisor(conn:=100, service_type:='oltp', cpu:=4, memory:=8192);
-- work_mem 16MB→64MB;max_parallel_workers_per_gather 当前 2→4(本机 4 核够用)
七、EXPLAIN 确认根因
EXPLAIN (ANALYZE, BUFFERS)
SELECT o.region, p.category, count(*) cnt, avg(o.amount) avg_amt
FROM orders o JOIN products p ON o.product_id=p.product_id
JOIN users u ON o.user_id=u.user_id
WHERE o.order_time>='2025-06-01' AND o.status='N'
GROUP BY o.region, p.category ORDER BY cnt DESC;
orders 走全表 Seq Scan,Filter 过滤掉 2359782 行、留 2640218 行参与 JOIN,正好印证数据分布(6 月后约 58.6% × ‘N’ 占 90% ≈ 264 万行)。Buffers read=51230 远大于 hit,物理读严重,Execution Time 9823 ms。
八、sys_hypo:先模拟再建
假设索引只对当前会话有效,且定义里表名不能带 schema 点号(否则报 syntax error),靠 search_path 定位表:
SET search_path = perf_demo, public;
SELECT sys_hypo_create_index('CREATE INDEX idx_hypo ON orders(order_time, status)');
SELECT * FROM sys_hypo_index;
再跑上面的 EXPLAIN,计划变成 Bitmap Heap Scan + Bitmap Index Scan,Execution Time 2345 ms(4.2×),Buffers read 从 51230 掉到 3120。模拟值和后面真实建索引的结果几乎一致。用完清掉:
SELECT sys_hypo_reset();
九、落地真实索引
CREATE INDEX CONCURRENTLY idx_orders_ordertime_status ON perf_demo.orders(order_time, status);
CREATE INDEX CONCURRENTLY idx_orders_region_amount ON perf_demo.orders(region, amount);
ANALYZE perf_demo.orders;
两个索引加主键一共 317 MB(112 + 98 + 107)。重跑 EXPLAIN 验证:Execution Time 2312 ms,Buffers read=2089。
十、参数再榨一层
work_mem 默认 16MB,大排序会溢盘。查询 6 在 16MB 下 Sort Method: external merge Disk: 289MB, 4523 ms;会话内 SET work_mem='64MB' 后变 quicksort Memory: 58MB, 1234 ms(3.7×)。
并行查询:max_parallel_workers_per_gather 是会话级参数可直接 SET,但 max_parallel_workers 是 SIGHUP,只能改 kingbase.conf 后 reload(本机默认 8 够用,不动)。
SET max_parallel_workers_per_gather = 2;
SET parallel_setup_cost = 100;
SET parallel_tuple_cost = 0.03;
再跑查询 4:拉起 2 个 worker,Execution Time 1234 ms。
十一、sys_dump 逻辑备份
调优完要做迁移前备份,金仓的 sys_dump 对标 pg_dump:
./sys_dump -Usystem -d test -p 54321 -Fc -f /tmp/test_backup.dump # 187MB(原数据 412MB+)
./ksql -Usystem -d test -p 54321 -c "CREATE DATABASE test_restore;"
./sys_restore -Usystem -d test_restore -p 54321 /tmp/test_backup.dump
恢复到 test_restore 后比对行数,users/products/orders 仍是 100000 / 10000 / 5000000,一致。生产建议:每周一次 sys_dump 全量 + 每天一次 sys_rman 物理增量,逻辑备份用于跨版本迁移和单表恢复,物理备份用于快速全库恢复。
十二、日常收尾与踩坑
调优完别撒手,VACUUM 和 autovacuum 让它自己跑:
autovacuum = on
autovacuum_analyze_scale_factor = 0.05
autovacuum_vacuum_scale_factor = 0.10
踩过的坑,列几条最值得记的:
坑现象解法KWR 报告空kwr_report() 返回空sys_kwr.enable=on 没开KSH 没数据改 collect_ksh 不生效共享库需重启,reload 不行KDDM 报错unsupported formatKDDM 只支持 TEXT假设索引无效EXPLAIN 计划没变只对当前会话,挂和查要同一会话假设索引报错syntax error at "."索引定义里表名别带 schema 点号并行不生效SET 了还报错max_parallel_workers 是 SIGHUP,得改 conf
结果汇总
阶段单次执行提升原始(无索引)9823 ms基线+ 复合索引2312 ms4.2×+ work_mem 64MB1234 ms8.0×
还有一组对照:压测区间总 DB Time 8234 s → 优化后 2956 s(-64%),头号 SQL 总耗时 3421 s → 803 s(-77%),DataFileRead 等待次数 156234 → 31200(-80%)。
KWR 看大盘找方向、KSH 看细节定位时刻、KDDM 直接给建议,三件套对标 Oracle 的 AWR/ASH,有 Oracle 经验的 DBA 上手很快,报告里的数和 EXPLAIN 实测能对上。sys_hypo 是亮点——不落盘就能试索引,模拟值和真实建完几乎一致,比盲目建索引省事。sys_dump + sys_rman 覆盖大部分备份场景。这次头号 SQL 从 9.8 秒压到 1.2 秒,但数据涨到几千万行时大概率还得再来一轮。养成"优化前拍快照、优化后拍快照、diff 看效果"的习惯,比任何调优技巧都实在。
附录:复现脚本(精简版)
```
!/bin/bash
以 kingbase 用户运行;前置:已装库、已配 shared_preload_libraries 并重启
KSBIN=/opt/kingbase/install/Server/bin
KSUSER=system; KSDB=test; KSPORT=54321
1) 扩展
$KSBIN/ksql -U$KSUSER -d $KSDB -p $KSPORT -c "CREATE EXTENSION IF NOT EXISTS sys_kwr; CREATE EXTENSION IF NOT EXISTS sys_stat_statements; CREATE EXTENSION IF NOT EXISTS sys_hypo;"2) 建表 + 造数(见正文第二节 SQL,略)
3) 快照1 -> 压测 -> 快照2
$KSBIN/ksql -U$KSUSER -d $KSDB -p $KSPORT -c "SELECT perf.create_snapshot();"开 5 个终端: bash /tmp/run_perf.sh
结束后:
$KSBIN/ksql -U$KSUSER -d $KSDB -p $KSPORT -c "SELECT perf.create_snapshot();"4) 报告
$KSBIN/ksql -U$KSUSER -d $KSDB -p $KSPORT -c "\copy (SELECT FROM perf.kwr_report(1,2,'html')) TO '/tmp/kwr.html' WITH (FORMAT TEXT);" $KSBIN/ksql -U$KSUSER -d $KSDB -p $KSPORT -c "SELECT FROM perf.kddm_report(1,2);"5) 假设索引模拟(同会话)
$KSBIN/ksql -U$KSUSER -d $KSDB -p $KSPORT暂时整理到这里。以上都是个人理解,可能有疏漏,欢迎指正。
评论 (0)
暂无评论