一、调优到底要解决什么问题?
别一上来就埋头苦干,先搞清楚我们要解决的核心问题。在KingbaseES里,调优主要就是两个方向:
- 让用户等得不那么着急
比如电商网站查订单,用户点一下要等半分钟才出结果,那肯定要被骂死。这时候就得想办法,比如开启并行查询(parallel_query
参数),让系统多开几个线程同时干活,原来一个人搬砖现在四个人一起搬,速度自然快了。 - 让服务器资源用得更值
有时候不是追求单个查询快,而是希望同样的机器能处理更多任务。比如跑批量报表,与其让CPU闲着,不如调调work_mem
参数,给排序操作多分点内存,减少磁盘读写,整体效率就上去了。
二、工具箱里都有啥好东西?
KingbaseES自带了一套诊断工具,不用额外装什么软件,开箱即用:
工具名称 | 干什么用的 | 什么时候用 |
---|---|---|
sys_stat_statement | 记录每条SQL跑了多少次、花了多少时间 | 想知道哪些SQL最拖后腿 |
kbbadger | 把日志文件变成好看的图表 | 做性能报告给领导看 |
SYS_KWR | 给数据库拍快照,记录某个时间点的状态 | 对比优化前后的效果 |
SQL_MONITOR | 实时看正在跑的SQL用了多少资源 | 发现有SQL在搞破坏时紧急处理 |
实用小技巧 :
想找出最近一小时最慢的10条SQL,直接跑这个:
sql
SELECT queryid, query, total_time, calls
FROM sys_stat_statements
WHERE query NOT LIKE '%sys_stat_statements%' -- 别把自己这条查询也算进去
ORDER BY total_time DESC
LIMIT 10;
AI写代码sql
12345
三、KingbaseES是怎么优化SQL的?
这个数据库的优化器挺聪明,分两步走:先让SQL本身变得更合理,再选择最好的执行方式。
1. 让SQL写得更聪明一点
数据库会自动帮你改写SQL,让它跑得更快:
-
把子查询变成表连接
比如你写了这样的查询:
sql-- 你写的 SELECT * FROM t1 WHERE EXISTS(SELECT 1 FROM t2 WHERE t1.a = t2.b); -- 数据库实际执行的(差不多是这个意思) SELECT t1.* FROM t1 INNER JOIN t2 ON t1.a = t2.b GROUP BY t1.id; AI写代码sql 12345
这个功能默认是开的,如果不想要可以关掉
kdb_rbo.enable_subquery_lift
。 -
外连接变内连接
有时候你写了LEFT JOIN,但后面的WHERE条件其实保证了右表肯定有数据,数据库就会自动改成普通的JOIN,效率更高。
-
把过滤条件往下推
如果你用了UNION ALL,数据库会尽量把WHERE条件推到每个子查询里,这样扫描的数据就少了。
2. 选择最好的执行方式
数据库会根据表的大小、索引情况来选择怎么执行:
-
统计信息要准确
数据库需要知道每个表大概有多少行、数据分布怎么样,才能做出正确判断。如果数据变化比较大,记得手动更新一下:
sqlANALYZE VERBOSE t1; -- 加上VERBOSE能看到详细过程 AI写代码sql 1
-
连接方式自动选择
- 小表连大表:用嵌套循环,就像两层for循环
- 大表连大表:用哈希连接,先把小一点的表放内存里建个哈希表
- 两个表都已经排好序:用合并连接,像合并两个有序数组
-
并行执行
对于大表扫描,可以开启并行处理:
iniSET max_parallel_workers_per_gather = 4; -- 最多用4个并行worker AI写代码sql 1
四、实战案例:30秒的查询怎么优化到1秒?
前段时间遇到一个典型案例,查询近30天活跃用户的订单,SQL跑了30秒才出结果。
原始SQL长这样:
sql
SELECT u.name, o.order_id
FROM users u, orders o
WHERE u.id = o.user_id
AND o.create_time >= CURRENT_DATE - INTERVAL '30 days'
AND u.status = 'active';
AI写代码sql
12345
第一步:看看执行计划
先用EXPLAIN ANALYZE
看看数据库是怎么执行的:
sql
EXPLAIN ANALYZE
SELECT u.name, o.order_id
FROM users u, orders o
WHERE u.id = o.user_id
AND o.create_time >= CURRENT_DATE - INTERVAL '30 days'
AND u.status = 'active';
AI写代码sql
123456
结果发现几个问题:
- orders表在做全表扫描(Seq Scan),这就是慢的根本原因
- 用的是嵌套循环连接(Nested Loop),对大表来说效率不高
- 数据库估计有10万行,实际却有50万行,统计信息明显不准
第二步:对症下药
-
先更新统计信息
iniANALYZE orders; AI写代码sql 1
-
建索引
scss-- 订单表按创建时间建索引,这样过滤30天内的数据就快了 CREATE INDEX idx_orders_create_time ON orders(create_time); -- 用户表建个覆盖索引,把需要的字段都包含进去 CREATE INDEX idx_users_status_id ON users(status, id) INCLUDE (name); AI写代码sql 12345
-
调整连接方式
对于这种大表连接,哈希连接通常更快:
iniSET enable_nestloop = off; -- 临时关闭嵌套循环 AI写代码sql 1
第三步:验证效果
优化后再跑一遍,执行时间从30秒降到了1.2秒!执行计划也变了:
- orders表改用索引扫描了
- 连接方式变成了哈希连接
- 数据库的行数估计也准确了
五、一些高级技巧
1. 分区表
对于特别大的表(比如几千万上亿行),可以按时间分区:
sql
CREATE TABLE orders (
order_id INT,
create_time DATE
) PARTITION BY RANGE (create_time);
-- 每个月一个分区
CREATE TABLE orders_202301 PARTITION OF orders FOR VALUES FROM ('2023-01-01') TO ('2023-02-01');
AI写代码sql
1234567
这样查询的时候只扫描相关分区,速度快很多。
2. 物化视图
对于那些经常跑的复杂报表,可以预先算好结果:
sql
CREATE MATERIALIZED VIEW mv_daily_sales AS
SELECT DATE_TRUNC('day', create_time) AS day, SUM(amount) AS total
FROM orders
GROUP BY day;
-- 定期刷新数据
REFRESH MATERIALIZED VIEW mv_daily_sales;
AI写代码sql
1234567
3. 强制指定执行计划
有时候数据库的选择不够理想,可以用HINT强制指定:
sql
SELECT /*+ USE_HASH(u o) */ u.name, o.order_id
FROM users u, orders o
WHERE u.id = o.user_id;
AI写代码sql
123
六、总结
搞SQL调优其实就是个"发现问题-分析原因-解决问题-验证效果"的循环过程。KingbaseES在这方面做得还不错,该有的工具都有,该有的优化功能也都有。
关键是要养成好习惯:
- 定期用
kbbadger
看看整体性能趋势 - 用
SQL_MONITOR
实时监控异常查询 - 重要的表记得及时更新统计信息
- 索引该建的要建,但也别建太多