PostgreSQL学徒

备库是否有自己的统计信息?(续)

Word count: 1.3kReading time: 5 min
2023/11/23
loading

前言

今天③群里有位群友提了这样一个问题,引起了我的思考

问个小白问题,pg_stat_all_indexes 的信息,重启或主备切换后数据是重新收集的吗?

我直接拍脑袋不假思索就回复了 “reset了”。然后这位群友就顺着回复我

近期有主备切换,那依据里面的信息来判断索引是否有使用是不可靠的

我后面转念一想,才发现这个回复是错误的,不严谨,尽早写出来加深印象,以免遗忘。

分析

简单验证一下,主库上建个表并使用索引扫描

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
postgres=# create table tbl1(id int);
CREATE TABLE
postgres=# insert into tbl1 values(generate_series(1,100000));
INSERT 0 100000
postgres=# create index on tbl1(id);
CREATE INDEX
postgres=# explain analyze select * from tbl1 where id = 99;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------
Index Only Scan using tbl1_id_idx on tbl1 (cost=0.29..4.31 rows=1 width=4) (actual time=0.026..0.028 rows=1 loops=1)
Index Cond: (id = 99)
Heap Fetches: 0
Planning Time: 0.100 ms
Execution Time: 0.053 ms
(5 rows)

postgres=# select * from pg_stat_all_indexes where relname = 'tbl1';
-[ RECORD 1 ]-+------------------------------
relid | 58029
indexrelid | 58032
schemaname | public
relname | tbl1
indexrelname | tbl1_id_idx
idx_scan | 1
last_idx_scan | 2023-11-22 22:38:59.827939+08
idx_tup_read | 1
idx_tup_fetch | 0

此处的 last_idx_scan 是 16 引入的新特性,可以让我们获取最近未使用过的索引列表,在以前的版本是无法直接查找在给定时间段内是否使用过某个索引的。

再去备库上查看一下

1
2
3
4
5
6
7
8
9
10
11
12
postgres=# select pg_is_in_recovery(),* from pg_stat_all_indexes where relname = 'tbl1';
-[ RECORD 1 ]-----+------------
pg_is_in_recovery | t
relid | 58029
indexrelid | 58032
schemaname | public
relname | tbl1
indexrelname | tbl1_id_idx
idx_scan | 0
last_idx_scan |
idx_tup_read | 0
idx_tup_fetch | 0

可以看到,数据并不一致,主库上使用索引故 idx_scan 为 1,备库上为 0。其实仔细思考🤔一下也不难理解:备库是可以承载查询的,因此备库有一套”独立”的统计信息并不奇怪。同理,pg_stat_database、pg_stat_bgwriter、pg_stat_user_functions等等,主备的数据都可以不一致。

但是有个较为特别的”视图”,没错——pg_stat_all_tables,我之前也曾写过一篇有关备库统计信息的文章 备库是否有自己的统计信息?里面也提及过相关底层知识:

pg_stat_all_tables 并不是一个普通的表,通过 \d+ 可以看到其定义,它只是一个视图,本质上就是一系列函数,有关表上各个维度的计数器。这些函数是与 stats collector 打交道的,每个后端进程事务提交/回滚时会发消息给进程 stats collector,stats collector 会汇总这份信息并记录到文件中,因此 stats collector 使用的是自己的文件,而不是普通的表进行存储,因此该数据不会被复制到备库。另外一个细节是 pg_basebackup 在复制的时候会忽略 pg_stat_tmp/pg_stat 目录下的内容 (15以前的版本)。

因此,这里就会有个很微妙的问题:试想一下,如果发生了主从切换,旧主上的这部分统计信息就”丢失”了,只有当新主上发生了新的活动才会汇总到 pg_stat_all_tables 中,那么假如原来旧主上有个表即将达到 vacuum/analyze 的触发阈值,但是由于主从切换,那么这个表就会”膨胀”,亦或是使用陈旧过时的统计信息,需要等到下一次触发阈值才会进行清理/分析,所以这也是一个潜在的微妙问题。

至于另外一个 pg_statistic,由于是统计信息表,但它是一个普通的表,因此也会被正常复制到备库,这一点无需担心主从切换后统计信息丢失。

pg_stat_reset

那么我当时为什么会拍脑袋回复”reset”呢?PostgreSQL 提供了一系列函数来重置相关统计信息,比如

  1. pg_stat_reset:Resets all statistics counters for the current database to zero,将全部统计信息置为零
  2. pg_stat_reset_single_table_counters:将某个表或索引的统计信息置为零
  3. pg_stat_reset_shared:pg_stat_wal、pg_stat_bgwriter、pg_stat_archiver等统计信息置为零

那么清零有什么后果呢?前面也提到了,autovacuum launcher 进程轮询所有数据库,并根据计数以及设置的阈值判断是否需要对表进行 vacuum/analyze,因此,重置之后可能会引发表膨胀/冻结等问题。

当然并不是说该函数一无是处,典型场景是调整了某些参数,想对比调整前后的状态,根据数据库的这些指标变化趋势进行对比,比如调整之后死锁的数量、数据库的active_time、缓冲区命中率等等,就可以考虑使用 pg_stat_reset 重置,还有个典型例子就是 pg_stat_statements_reset(),定时清空,分析潜在的高危查询。

小结

因此,让我们更加严谨地回答这位群友的问题:主备除了 pg_stat_all_tables 之外都要自己独立的”统计信息”,因此主从切换之后,这些计数都是重新计算累积的,为了更好分析潜在的问题,可以按需重置,但是要注意可能的危害。

另外今天晚上看了一下 17 的进度,暂时没有惹眼特性,indexes_total还算吸引我。

参考

https://developer.aliyun.com/article/241192

https://stackoverflow.com/questions/64555839/when-and-why-should-i-trigger-pg-stat-reset

https://aws.amazon.com/cn/blogs/china/understanding-statistics-in-postgresql/

https://blog.csdn.net/longailk/article/details/118811793

https://www.postgresql.org/docs/15/monitoring-stats.html

CATALOG
  1. 1. 前言
  2. 2. 分析
  3. 3. pg_stat_reset
  4. 4. 小结
  5. 5. 参考