“治未病”的概念最早出现于《黄帝内经》,在《素问·四气调神大论》中提出:“是故圣人不治已病治未病,不治已乱治未乱,此之谓也。 夫病已成而后药之,乱已成而后治之,譬犹渴而穿井,斗而铸锥,不亦晚乎”,就生动地指出了“治未病”的重要意义。
数据库优化固然重要, 但这是治已病, 未病则更加重要. 未病建议参考:
单一SQL慢, 比较容易解决, 从执行计划入手即可, 是否执行计划不正确, 是否索引未创建或不合理, 是否需要改写SQL, 是否有膨胀, 是否存在业务逻辑导致的长时间锁冲突, 是否SQL过于复杂需要固定执行计划或者采用更高级的优化器.
常用分析工具与方法:
explain, 分析执行计划
perf, 分析单条SQL(或函数)执行时的代码瓶颈
锁等待分析
查询 其他会话中正在运行的SQL memory context
show 其他会话中正在运行的SQL的执行计划
指定、固定、篡改执行计划
数据库存储组织、数据库索引组织、优化器算法、数据扫描方法等原理
例子, 查询所有传感器上报数据的最新值:
create unlogged table tbl_log (gid int, info text, crt_time timestamp);
insert into tbl_log select random()*10, md5(random()::Text), clock_timestamp() from generate_series(1,5000000);
select gid,info,crt_Time from
(select *, row_number() over (partition by gid order by crt_time desc) as rn from tbl_log) t
where rn=1;
gid | info | crt_time
-----+----------------------------------+----------------------------
0 | 144ccff07b812d0ca5252ae8cbc2ad50 | 2022-08-23 14:59:59.531316
1 | 22fb4e6bb2daa15fcb8b00358bb4f3ad | 2022-08-23 14:59:59.531342
2 | 43761591e939309f1bb9e2b94f642e6d | 2022-08-23 14:59:59.531356
3 | 1751a3a7884685ec2c16926b4e2ad607 | 2022-08-23 14:59:59.531341
4 | 5df93803d19bf3a6bd19b7d017757bed | 2022-08-23 14:59:59.531348
5 | c11384fa2434c67992d14da837f65ac0 | 2022-08-23 14:59:59.531352
6 | ea33278a5f8d75c75ddbcbf7d753367f | 2022-08-23 14:59:59.531355
7 | c98c67d0a08c2f6dc865a291997748d5 | 2022-08-23 14:59:59.531347
8 | 644215ca6c3f2ad0fc1c0387a8e5c4fb | 2022-08-23 14:59:59.53133
9 | d0b554588b4a1d3de9fddcac630234ea | 2022-08-23 14:59:59.531354
10 | 903c0dda9ddfbd241043b8d75b4eaf22 | 2022-08-23 14:59:59.531351
(11 rows)
Time: 2230.696 ms (00:02.231)
查看数据结构
postgres=# \d tbl_log
Table "public.tbl_log"
Column | Type | Collation | Nullable | Default
----------+-----------------------------+-----------+----------+---------
gid | integer | | |
info | text | | |
crt_time | timestamp without time zone | | |
查看SQL执行计划:
返回11行记录(
rows=11
), 但是扫描了将近20万个数据块(
shared hit=16167 read=30562, temp read=72167 written=72315
, 耗时
707.021
毫秒), 并且使用了外部排序(
external merge Disk: 288672kB
, 耗时
4382.093-707.021
毫秒).
explain (analyze,verbose,timing,costs,buffers) select gid,info,crt_Time from
(select *, row_number() over (partition by gid order by crt_time desc) as rn from tbl_log) t
where rn=1;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------
Subquery Scan on t (cost=1342550.98..1505051.08 rows=25000 width=45) (actual time=4382.105..5406.218 rows=11 loops=1)
Output: t.gid, t.info, t.crt_time
Filter: (t.rn = 1)
Buffers: shared hit=16167 read=30562, temp read=72167 written=72315
-> WindowAgg (cost=1342550.98..1442551.04 rows=5000003 width=53) (actual time=4382.103..5406.203 rows=11 loops=1)
Output: tbl_log.gid, tbl_log.info, tbl_log.crt_time, row_number() OVER (?)
Run Condition: (row_number() OVER (?) <= 1)
Buffers: shared hit=16167 read=30562, temp read=72167 written=72315
-> Sort (cost=1342550.98..1355050.99 rows=5000003 width=45) (actual time=4382.093..4997.855 rows=5000000 loops=1)
Output: tbl_log.gid, tbl_log.crt_time, tbl_log.info
Sort Key: tbl_log.gid, tbl_log.crt_time DESC
Sort Method: external merge Disk: 288672kB
Buffers: shared hit=16167 read=30562, temp read=72167 written=72315
-> Seq Scan on public.tbl_log (cost=0.00..96729.03 rows=5000003 width=45) (actual time=0.026..707.021 rows=5000000 loops=1)
Output: tbl_log.gid, tbl_log.crt_time, tbl_log.info
Buffers: shared hit=16167 read=30562
Planning Time: 0.092 ms
Execution Time: 5507.738 ms
(18 rows)
Time: 5508.182 ms (00:05.508)
建
gid, crt_time desc
索引.
postgres=# create index idx_tbl_log_1 on tbl_log (gid,crt_time desc);
CREATE INDEX
Time: 3530.425 ms (00:03.530)
重新查询后, 使用了索引, 但是性能并没有提升多少. 避免了外部排序, 但是依旧有大量的扫描(
shared hit=16266 read=517194 written=8941
, 耗时
2736.351
毫秒).
explain (analyze,verbose,timing,costs,buffers) select gid,info,crt_Time from
(select *, row_number() over (partition by gid order by crt_time desc) as rn from tbl_log) t
where rn=1;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------
Subquery Scan on t (cost=0.43..488005.99 rows=25000 width=45) (actual time=0.036..3116.007 rows=11 loops=1)
Output: t.gid, t.info, t.crt_time
Filter: (t.rn = 1)
Buffers: shared hit=16266 read=517194 written=8941
-> WindowAgg (cost=0.43..425505.99 rows=5000000 width=53) (actual time=0.035..3115.996 rows=11 loops=1)
Output: tbl_log.gid, tbl_log.info, tbl_log.crt_time, row_number() OVER (?)
Run Condition: (row_number() OVER (?) <= 1)
Buffers: shared hit=16266 read=517194 written=8941
-> Index Scan using idx_tbl_log_1 on public.tbl_log (cost=0.43..338005.99 rows=5000000 width=45) (actual time=0.026..2736.351 rows=5000000 loops=1)
Output: tbl_log.gid, tbl_log.crt_time, tbl_log.info
Buffers: shared hit=16266 read=517194 written=8941
Planning:
Buffers: shared hit=18 read=1 dirtied=2
Planning Time: 0.630 ms
Execution Time: 3116.041 ms
(15 rows)
为了解决扫描的问题, 引入递归查询, 需要修改SQL.
扫描降低到了47个block, 同时避免了排序. 整体SQL耗时从
5508.182
毫秒降低到了
0.6
毫秒.
甚至你要了解数据分布, 扫描方法; 掌握数据库的基本原理(存储结构、索引结构、扫描优化器算法等)对优化是非常有帮助的, 可以帮助你从根源找问题并提出优化思路.
7 固定、篡改、保存执行计划. (对于SQL不能修改的场景, 解决SQL因执行计划不正确产生的问题.)
数据库整体变慢, 最核心的是定位罪魁祸首, 进行优化, 最后需要判断是否在业务层面、数据库SQL层面、数据库内核层面 都已经无法优化(则可能需要升级硬件、或者使用分布式数据库、扩容等),
常用分析工具与方法:
pg_stat_statements, 找出最消耗资源的TOP SQL
performance insight, 活跃会话、等待事件采样快照记录, 用于分析活跃会话数超过CPU 核数的时间段, 当时数据库系统的等待情况. SQL的分布等.
perf, 借助抓取系统调用、用户进程函数调用等统计信息, 生成数据库高峰、或者问题时刻的代码级耗时统计的火焰图, 找到代码级别的瓶颈. 通常用于分析表面上很难察觉的问题.
pg_stat_ pg_statio_ 统计信息, 找出CPU消耗、IO消耗不合理的表
找出膨胀索引与膨胀表, 垃圾清理不及时的原因分析
找出统计信息偏差, 配置自动收集统计信息
参数不正确、优化器校准因子不正确等问题
偶尔某些很快的SQL会抖动(变得很慢). 针对这个情况, 需要找到这条SQL变慢的时刻, 当时数据库的整体资源消耗的情况, 以及当时这条SQL的执行计划、锁等待的情况.
比较典型的例如prepare, 输入参数不同可能会有不一样的资源消耗, 或者执行计划不正确导致.
又或者遇到较长的锁等待. (包括低级锁lwlock、或者高级锁lock)
常用分析工具与方法:
auto_explain, 记录执行时间超过阈值的SQL、函数的执行计划, 执行过程的完整数据(buffer, hit, read, write, rows, time等)
log_lock_waits, 记录锁等待时间超过lock_timeout的SQL, 以及堵塞它的PID.
performance insight, 活跃会话、等待事件采样快照记录, 用于分析SQL抖动的对应时间段的数据库实例整体情况、SQL当时的等待情况.
例子, 使用pg_stat_activity活跃会话快照, 分析过去抖动时刻的慢SQL等待事件:
除了有问题后再分析, 在问题发生前也可以做很多事情, 例如环境部署、参数配置都很重要. 可以在我的github里搜索相关文章.