自省 自行 自醒

统计信息中几个鲜为人知的原理

Word count: 1.3kReading time: 4 min
2024/06/29
loading

前言

关于统计信息的文章,我之前也写过了很多篇,在我的 Github 上有合订本

  1. 深入浅出统计信息内核原理(上):Compressed Histogram
  2. 深度剖析PostgreSQL中的统计信息
  3. 备库是否有自己的统计信息?
  4. 备库是否有自己的统计信息?(续)
  5. 从实际案例分析PostgreSQL中的统计信息
  6. 小案例之鸡生蛋还是蛋生鸡

最近在翻译过程中,又了解到许多鲜为人知的内核原理。

统计信息的采集

首先,统计信息由谁来采集,何时采集?How?Who?When?在 14 以前,统计信息的收集由一个专门的 stats collector 进程来收集,在 15 之后移除了,因为⽂件写⼊的频率相当频繁 (最多每秒两次),假设有 1K 个数据库,每个数据库有 100 个表和 1K 个索引,可能会有约 50MB 的统计数据。每秒写⼊两次,就是每秒约 100MB 的写⼊量,再加上时不时读取⼀下这些统计信息,在 9.3 以前更甚,所有的统计信息数据都是汇聚在⼀个的单个⽂件中。

除此之外,像一些基础级的统计信息,比如 pg_class 中的 relpages、reltuples、relallvisible 等字段,执行 vacuum full / cluster/ create index 等 DDL 时也会进行收集,所以比如我们要观察模拟没有统计信息时,执行计划是怎么走的时候,也要注意这些命令。

自动缩放

如果规划器发现 relpages 和实际文件大小之间存在差距,那么它会缩放 reltuples 值以提高预估的准确性。举个栗子

1
2
3
4
5
6
7
8
9
postgres=# create table big(id int,info text) with (autovacuum_enabled = off);
CREATE TABLE
postgres=# insert into big select n,md5(random()::text) from generate_series(1,1000000) as n;
INSERT 0 1000000
postgres=# select reltuples,relpages from pg_class where relname = 'big';
reltuples | relpages
-----------+----------
-1 | 0
(1 row)

此处由于我关闭了表级的自动收集,因此基础统计信息还没有数据。注意此处的 reltuples = -1,这个值主要用于区分尚未分析的表和没有任何行的真正意义上的空表。

让我们收集一下统计信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
postgres=# analyze big;
ANALYZE
postgres=# select reltuples,relpages from pg_class where relname = 'big';
reltuples | relpages
-----------+----------
1e+06 | 8334
(1 row)

postgres=# explain select * from big;
QUERY PLAN
--------------------------------------------------------------
Seq Scan on big (cost=0.00..18334.00 rows=1000000 width=37)
(1 row)

由于我是 select *,因此预估的 rows 直接取自 reltuples。注意,表级我已经关闭了 autovacuum。现在,让我们扩大一倍行数

1
2
3
4
5
6
7
8
9
10
11
12
13
postgres=# insert into big select * from big ;
INSERT 0 1000000
postgres=# select reltuples,relpages from pg_class where relname = 'big';
reltuples | relpages
-----------+----------
1e+06 | 8334
(1 row)

postgres=# explain select * from big;
QUERY PLAN
--------------------------------------------------------------
Seq Scan on big (cost=0.00..36665.80 rows=1999880 width=37)
(1 row)

可以看到,虽然 pg_class 中的数据是过时的,但是规划器预估的 rows 翻倍了。**由于与 relpages 相比文件大小增加了一倍,规划器调整了预估的行数,但是这种调整可能并不总是准确,因为规划器假设数据密度保持不变。**这种问题和 limit 1 就有点异曲同工的味道了。

非重复值

pg_stats 视图中的 n_distinct 字段显示了列中非重复值的数量。但是值得注意的是,如果非重复值的预估数量超过了总行数的 10%,那么分析器会使用一个比例值来显式;在这种情况下,进一步的数据更新不太可能改变这个比例值,感兴趣的读者可以阅读 compute_distinct_stats。

1
2
3
4
5
6
7
8
/*
* If we estimated the number of distinct values at more than 10% of
* the total row count (a very arbitrary limit), then assume that
* stadistinct should scale with the row count rather than be a fixed
* value.
*/
if (stats->stadistinct > 0.1 * totalrows)
stats->stadistinct = -(stats->stadistinct / totalrows);

如果我们预估不同值的数量超过了总行数的 10% (一个非常任意的限制),则假设 stadistinct 应该随行数缩放,而不是固定值。

备库的统计信息

当架构是主从流复制的时候,备库的统计信息是怎样的?关于这个内容,我之前也写过原理解析

简而言之,备库是可以承载查询的,因此备库有一套”独立”的统计信息并不奇怪。同理,pg_stat_database、pg_stat_bgwriter、pg_stat_user_functions等等,主备的数据都可以不一致。至于 pg_statistic,由于是统计信息表,但它是一个普通的表,因此也会被正常复制到备库,这一点无需担心主从切换后统计信息丢失。那么 pg_stat_all_tables 呢?各位读者可以自行验证与分析。

小结

统计信息的重要性不言而喻,我之前在杭州公开课上也分享过统计信息的原理和实战,错过的读者可以听一下回放。

杭州活动回放观看链接🔗:

  1. 公开课——国产数据库共话未来趋势(上午场)回放观看地址:https://live.csdn.net/room/wl5875/owIpOavL
  2. 国产数据库共话未来趋势(下午场)回放观看地址:https://live.csdn.net/room/wl5875/JCVcH6E4
CATALOG
  1. 1. 前言
  2. 2. 统计信息的采集
  3. 3. 自动缩放
  4. 4. 非重复值
  5. 5. 备库的统计信息
  6. 6. 小结