时间:2020-07-21来源:www.pcxitongcheng.com作者:电脑系统城
对于sql优化,除了索引之外,执行计划和统计信息是无法绕开的一个话题,如果sql优化(所有的RDBMS)脱离了统计信息的话就少了一个为什么的过程,味道就感觉少了一大半。
刚接触Postgresql,粗浅地学习总结一下Postgresql相关的统计信息。
负载指标由上文提到的stats collector进程来实时收集更新。PostgreSQL的统计数据收集器是一个支持收集和报告服务器活动信息的子系统。
目前,收集器可以计算对磁盘块和单行项中的表和索引的访问次数。
它还跟踪每个表中的总行数,以及关于vacuum的信息,并分析每个表的操作。
同时还可以记录基于sql语句执行的代价信息。这部分与该主题关系不大,就不展开详述。
有两种VACUUM的变体:标准VACUUM和VACUUM FULL。
1,标准形式的VACUUM可以和生产数据库操作并行运行(SELECT、INSERT、UPDATE和DELETE等命令将继续正常工作,但在清理期间你无法使用ALTER TABLE等命令来更新表的定义)。
2,VACUUM FULL可以收回更多磁盘空间但是运行起来更慢,且vacuum full不会有后台进程主从触发(只能手动执行)。
另外,VACUUM FULL类似于表的重建或者说碎片整理,同时需要一个大小相当于原始表的额外空间。
要求在其工作的表上得到一个排他锁,因此无法和对此表的其他使用并行。因此,通常管理员应该努力使用标准VACUUM并且避免VACUUM FULL。
create table myschema.table_test ( c1 serial primary key, c2 int, c3 varchar(100), c4 varchar(100), c5 timestamp ) create or replace function random_string(integer) returns text as $body$ select upper(array_to_string(array(select substring('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' FROM (ceil(random()*62))::int FOR 1) FROM generate_series(1, $1)), '')); $body$ language sql volatile; insert into myschema.table_test (c2,c3,c4,c5) select cast(random()*500000 as int),random_string(10),random_string(10), cast( now()-'1 min'::interval * random()*500000 as timestamp ) from generate_series(1,1000000)
在库级别,pg_stat_databases用来描述描述"库"级别的摘要信息,包括库名,当前库事务提交次数,回滚次数,读写次数,死锁等等信息。
这些信息可以观察到到一个库的负载情况和健康状况。
在表级别,由pg_stat_user_tables来描述某个具体的表中的信息,包括增删查改的次数,数据行等摘要信息。
这些信息可以衡量一个表的冷热程度,活跃性,以及体量以及一些analyze时间相关的信息。
pg_class 来描述表的物理存储信息,包括数据行数,数据页的个数。
pg_stats用来描述一个表中所有的字段的数据分布信息,为执行计划决策提供依据,与SQLServer的直方图类似,熟悉的配方熟悉的味道,只有管理员账号能够访问
类似于SQLServer的统计信息+直方图,也即执行dbcc show_statistics(***,***)的效果,或者MySQL中的information_schema.column_statistics表中的信息(简直一模一样)。
这一点,SQLServer的直方图用一种相对比较直观的方式展示了出来。
select * from pg_stats WHERE tablename = 'table_test'; schemaname | myschema tablename | table_test attname | c1 inherited | f null_frac | 0 avg_width | 4 n_distinct | -1 most_common_vals | most_common_freqs | histogram_bounds | {15,9799,20037,30372,40276,……………………,990687,999949} correlation | 1 most_common_elems | most_common_elem_freqs | elem_count_histogram | schemaname | myschema tablename | table_test attname | c2 inherited | f null_frac | 0 avg_width | 4 n_distinct | -0.330106 most_common_vals | most_common_freqs | histogram_bounds | {23,4712,9677,14189,19403,………………490576,495541,499975} correlation | -0.00480835 most_common_elems | most_common_elem_freqs | elem_count_histogram | schemaname | myschema tablename | table_test attname | c5 inherited | f null_frac | 0 avg_width | 8 n_distinct | -0.993476 most_common_vals | most_common_freqs | histogram_bounds | {"2019-08-05 14:29:35.515329","2019-08-08 19:17:14.628418",……"2020-07-14 18:25:47.515329","2020-07-17 19:40:48.015329"} correlation | -0.00216757 most_common_elems | most_common_elem_freqs | elem_count_histogram | schemaname | myschema tablename | table_test attname | c4 inherited | f null_frac | 0 avg_width | 11 n_distinct | -1 most_common_vals | most_common_freqs | histogram_bounds | {0035UXPI6A,0N8JC5OIER,1BZZAU76H5,…………ZQSMJJRFWE,ZZZYYV9TKJ} correlation | -0.00186405 most_common_elems | most_common_elem_freqs | elem_count_histogram | schemaname | myschema tablename | table_test attname | c3 inherited | f null_frac | 0 avg_width | 11 n_distinct | -1 most_common_vals | most_common_freqs | histogram_bounds | {000XXEZ4HN,0N3GEAC1QS,…………,Z5ANIIBHDO,ZH6ZYR94CJ,ZQW7M2HZ4I,ZZZENAC3OQ} correlation | 0.00391295 most_common_elems | most_common_elem_freqs | elem_count_histogram | Time: 1.259 ms
关于pg_stat系统表的详细描述如下
名字 | 类型 | 引用 | 描述 |
schemaname | name | pg_namespace.nspname | 包含此表的模式名字 |
tablename | name | pg_class.relname | 表的名字 |
attname | name | pg_attribute.attname | 这一行描述的字段的名字 |
inherited | bool | 如果为真,那么这行包含继承的子字段,不只是指定表的值。 | |
null_frac | real | 记录中字段为空的百分比 | |
avg_width | integer | 字段记录以字节记的平均宽度 | |
n_distinct | real | 如果大于零,就是在字段中独立数值的估计数目。如果小于零, 就是独立数值的数目被行数除的负数。 用负数形式是因为ANALYZE 认为独立数值的数目是随着表增长而增长; 正数的形式用于在字段看上去好像有固定的可能值数目的情况下。比如, -1 表示一个唯一字段,独立数值的个数和行数相同。 |
|
most_common_vals | anyarray | 一个字段里最常用数值的列表。如果看上去没有啥数值比其它更常见,则为 null | |
most_common_freqs | real[] | 一个最常用数值的频率的列表,也就是说,每个出现的次数除以行数。 如果most_common_vals是 null ,则为 null。 | |
histogram_bounds | anyarray | 一个数值的列表,它把字段的数值分成几组大致相同热门的组。 如果在most_common_vals里有数值,则在这个饼图的计算中省略。 如果字段数据类型没有<操作符或者most_common_vals 列表代表了整个分布性,则这个字段为 null。 |
|
correlation | real | 统计与字段值的物理行序和逻辑行序有关。它的范围从 -1 到 +1 。 在数值接近 -1 或者 +1 的时候,在字段上的索引扫描将被认为比它接近零的时候开销更少, 因为减少了对磁盘的随机访问。 如果字段数据类型没有<操作符,那么这个字段为null。 |
|
most_common_elems | anyarray | 经常在字段值中出现的非空元素值的列表。(标量类型为空。) | |
most_common_elem_freqs | real[] | 最常见元素值的频率列表,也就是,至少包含一个给定值的实例的行的分数。 每个元素频率跟着两到三个附加的值;它们是在每个元素频率之前的最小和最大值, 还有可选择的null元素的频率。 当most_common_elems 为null时,为null) |
|
elem_count_histogram | real[] | 该字段中值的不同非空元素值的统计直方图,跟着不同非空元素的平均值。(标量类型为空。) |
开始之前,对比SQLServer和MySQL中统计信息的自动更新的出发情况,统计信息更新是一个非常有意思的话题。
SQLServer是表中的输入写入(增删改)超过阈值500 + (20 %×表数据总量)之后会自动触发更新,以为默认情况下可以认为这是一个写死的参数。
因为SQLServer统计信息的更新会有非常多的问题,虽然SQLServer有一个trace flag 2371
可以改变改规则,但也属于半遮半掩的一个非开放功能对于MySQL或者postgresql,类似所有的参数都是可配置化的,因此非常透明
MySQL是innodb_stats_auto_recalc打开的情况下,增删改的次数大于表中已有数据的10%之后主动触发更新。
2.1 自动更新的开关
首先autovacuum开关需要打开,也即上文中提到的autovacuum lancher进程实现,在表中的数据满足一定条件之后的定时更新
这里的autovacuum是这个自动化更新的开关。默认打开。
2.2关于自动更新的触发机制
也类似于MySQL,子线程会根据上下文,存在一个工作频率,postgresql在打开autovacuum基础上,
autovacuum进程执行统计信息更新的唤醒频率,以及工作线程数,依次对各个表执行并发清理,
autovacuum_naptime唤醒频率默认为1min,autovacuum_max_workers工作线程默认为3个,被唤醒的工作线程会并发对库中的满足更新条件的表进行统计信息更新。
2.3关于自动更新统计信息的阈值
这里会涉及两个参数autovacuum_vacuum_threshold和autovacuum_vacuum_scale_factor
anl_base_thresh默认值时50,anl_scale_factor默认值时0.2,这都是可配置的,而且是每个表可以独立配置的,这里难免会想到SqlServer的这个阈值也是类似变化数量超过500 + (20 %×表数据总量)
autovacuum进程进行 vacuum 触发条件表上增删改的行数 >= autovacuum_vacuum_scale_factor* reltuples(表上记录数) + autovacuum_vacuum_threshold
2.4关于自动更新的采样范围
这里涉及一个default_statistics_target参数,该参数默认值是100.范围是1~10000。2.5 统计信息最后一次更新之后的变化
统计信息更新日志,pg_stat_all_tables表存储了所有表的最后一次更新历史信息(last_analyza),以及最后一次更新之后数据发生的变化情况(n_mod_since_analyze),这是一个非常因吹斯汀的数据。
上面把统计信息各种阈值,各种触发条件七七八八地列举的差不多了,有没有表再回头关注最后一次统计信息更新之后表的基数的变化?
肯定是有必要的,上面说了,即便是default_statistics_target是一个可以调整的参数,但不一定知道具体哪个值是合理的或者说是可行的。
那么,就可以观察,在执行计划使用统计信息做预估,出现偏差的临界点,就需要重新收集更新统计信息了,
那么此时就可以结合pg_stat_all_tables此时举上次收集完统计信息变化的情况,来反推autovacuum_vacuum_scale_factor这个值,从而更加科学地去设置autovacuum_vacuum_scale_factor这个因子。
开始之前现提出一个问题:为什么需要手动创建统计信息?
通常情况下,统计信息在满足一定条件,且取样达到一定程度之后,可以得到一个相对准确的统计信息,一切看起来都是水到渠成。
但是不排除一些个特殊情况,需要手动创建统计信息来实现预估的准确性,比如数据倾斜严重的情况下,又难以100%取样(即便100%取样,统计信息还有一个“步长”的限制),
此时手动创建统计信息,从而更好指导执行计划的生成。
这一点SQLServer和Postgresql都是支持的,MySQL这一点是不支持的。
这个就稍微扯远一点,SQLServer中对于select * from table where c1 = m and c2 = n这种语句,返回行数是如何预估的?
如之前提到过的,假如c1的选择性为p1,c2的选择性为p2,表中的总行数为table_rowcount,暂忽略索引自身以及统计信息准确性带来的影响,以此为前提。
在SQLServer 2012中是预估返回函数是p1*p2*table_rowcount,
在SQLServer 2014或者更高版本中,这个算法发生了变化,是P1*P21/2*table_rowcount
简单demo一下,假设在c2和c3字段上某些条件值分布的特别不均匀(严重倾斜)的情况下,创建这么一个统计信息之后,可以指导执行计划在遇到类似的查询条件之后,做出更加准确的预估。
2022-09-11
Windows 系统 PostgreSQL 手工安装配置方法教程图解2022-02-25
系统城教小白如何在Centos8-stream安装PostgreSQL132021-04-22
自定义函数实现单词排序并运用于PostgreSQL(实现代码)