数据库的智慧之源 - 统计信息 之 自定义多维统计

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云原生数据库 PolarDB 分布式版,标准版 2核8GB
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介:

标签

PostgreSQL , 多列统计信息 , 多列唯一值 , 多列依赖度 , 任意维度TOP N


背景

PostgreSQL和Oracle一样,优化器是基于成本的估算。

成本估算中很重要的一个环节是估计每个执行节点返回的记录数。

例如两张表JOIN的时候,如果走HASH JOIN那么需要选择记录数少的那个作为哈希表。

又比如求多个字段的group by,评估返回多少条记录给上层节点。

对于基于单列统计的的柱状图,估算单个字段条件的选择性是很准确的,而估算多个字段时,PostgreSQL默认使用独立属性,直接以多个字段选择性相乘的方法计算多个字段条件的选择性。不是很准确。

PostgreSQL 10引入了一个黑科技功能,允许用户自定义多个字段的统计信息,目前支持多列相关性和多列唯一值两种统计。

由于多列统计涉及到许多组合(N阶乘种组合),因此默认不会对所有字段进行任意组合的统计,用户可以根据实际的业务需求,对需要and查询,组合group by的字段(例如 where a xx and b xx, group by a,b)。创建对应的自定义统计信息。

例子讲解

1、建表,11个字段。

postgres=# create table tbl(id int, c1 int, c2 text, c3 int, c4 int, c5 int, c6 int, c7 int, c8 int, c9 int, c10 int);    
CREATE TABLE  

2、写入测试数据,1000万条。

postgres=# insert into tbl select     
postgres-# id,     
postgres-# random()*100, substring(md5(random()::text), 1, 4), random()*900, random()*10000, random()*10000000,     
postgres-# random()*100000, random()*100, random()*200000, random()*40000, random()*90000     
postgres-# from generate_series(1,10000000) t(id);    
INSERT 0 10000000  

3、分析表

postgres=# analyze tbl;  
ANALYZE  

得到reltuples为1e+07,后面要用于计算。

postgres=# select reltuples from pg_class where relname='tbl';  
-[ RECORD 1 ]----  
reltuples | 1e+07  

4、SQL举例

4.1 单个字段条件

postgres=# explain (analyze) select * from tbl where c1=1;  
                                                 QUERY PLAN                                                    
-------------------------------------------------------------------------------------------------------------  
 Seq Scan on tbl  (cost=0.00..218458.08 rows=93865 width=45) (actual time=0.018..755.833 rows=99527 loops=1)  
   Filter: (c1 = 1)  
   Rows Removed by Filter: 9900473  
 Planning time: 0.077 ms  
 Execution time: 763.151 ms  
(5 rows)  

可以推算得到c1=1的选择性为: 93865/1e+07 。

postgres=# explain (analyze) select * from tbl where c2='abc';  
                                               QUERY PLAN                                                  
---------------------------------------------------------------------------------------------------------  
 Seq Scan on tbl  (cost=0.00..218458.08 rows=148 width=45) (actual time=874.473..874.473 rows=0 loops=1)  
   Filter: (c2 = 'abc'::text)  
   Rows Removed by Filter: 10000000  
 Planning time: 0.080 ms  
 Execution time: 874.505 ms  
(5 rows)  

可以推算得到c2='abc'的选择性为: 148/1e+07 。

4.2 多个字段条件

postgres=# explain (analyze) select * from tbl where c1=1 and c2='abc';  
                                              QUERY PLAN                                                 
-------------------------------------------------------------------------------------------------------  
 Seq Scan on tbl  (cost=0.00..243458.09 rows=1 width=45) (actual time=802.347..802.347 rows=0 loops=1)  
   Filter: ((c1 = 1) AND (c2 = 'abc'::text))  
   Rows Removed by Filter: 10000000  
 Planning time: 0.116 ms  
 Execution time: 802.374 ms  
(5 rows)  

rows=1是怎么得来的呢,在没有自定义统计信息时,是这么算的,算这两个条件完全不相干,所以选择性直接相乘。

(93865/1e+07) * (148/1e+07) * 1e+07 = 1.389202 ~= 1  

4.3 单个字段条件求唯一值

postgres=# explain (analyze) select c1,count(*) from tbl group by c1;  
                                                       QUERY PLAN                                                          
-------------------------------------------------------------------------------------------------------------------------  
 HashAggregate  (cost=243458.09..243459.10 rows=101 width=12) (actual time=3256.458..3256.473 rows=101 loops=1)  
   Group Key: c1  
   ->  Seq Scan on tbl  (cost=0.00..193458.06 rows=10000006 width=4) (actual time=0.013..1252.169 rows=10000000 loops=1)  
 Planning time: 0.061 ms  
 Execution time: 3256.518 ms  
(5 rows)  

rows=101来自pg_stats.n_distinct , tbl.c1列的统计。

n_distinct             | 101  

4.4 多个字段条件求唯一值

postgres=# explain (analyze) select c1,c2,count(*) from tbl group by c1,c2;  
                                                          QUERY PLAN                                                             
-------------------------------------------------------------------------------------------------------------------------------  
 GroupAggregate  (cost=1561215.43..1671215.50 rows=1000001 width=17) (actual time=11414.144..16549.549 rows=5147139 loops=1)  
   Group Key: c1, c2  
   ->  Sort  (cost=1561215.43..1586215.45 rows=10000006 width=9) (actual time=11414.132..13905.616 rows=10000000 loops=1)  
         Sort Key: c1, c2  
         Sort Method: external merge  Disk: 185984kB  
         ->  Seq Scan on tbl  (cost=0.00..193458.06 rows=10000006 width=9) (actual time=0.008..1567.909 rows=10000000 loops=1)  
 Planning time: 0.082 ms  
 Execution time: 16952.301 ms  
(8 rows)  

5、自定义统计信息语法讲解

Command:     CREATE STATISTICS  
Description: define extended statistics  
Syntax:  
CREATE STATISTICS [ IF NOT EXISTS ] statistics_name  
    [ ( statistic_type [, ... ] ) ]  
    ON column_name, column_name [, ...]  
    FROM table_name  

创建自定义统计信息,指定需要自定义统计的字段名,需要统计依赖性、唯一性(不指定则都统计)。

6、创建自定义统计信息

我们创建c1 c2 c3这三个字段的自定义统计信息。

postgres=# create statistics s1 on c1,c2,c3 from tbl;  
CREATE STATISTICS  

自定义统计信息创建好之后,需要分析表,才会生成。

postgres=# analyze tbl;  
ANALYZE  

7、自定义统计信息解读

postgres=# select * from pg_statistic_ext where stxname='s1';  
-[ RECORD 1 ]---+--------------------------------------------------------------------------------------------------------------  
stxrelid        | 16384   -- 表  
stxname         | s1  
stxnamespace    | 2200  
stxowner        | 10  
stxkeys         | 2 3 4  -- 表示第2,3,4列创建自定义统计信息。  
stxkind         | {d,f}   -- 统计 字段之间的依赖度(相关性)、唯一值个数。  
stxndistinct    | {"2, 3": 3747653, "2, 4": 87662, "3, 4": 9001205, "2, 3, 4": 10000006}   -- 组合唯一值个数  
stxdependencies | {"3 => 2": 0.642100, "3 => 4": 0.639567, "2, 3 => 4": 0.995000, "2, 4 => 3": 0.712033, "3, 4 => 2": 0.999667}    
-- 字段之间的依赖性,当使用多个字段AND条件时,用于代替多个孤立条件的选择性相乘。选择性乘以依赖度,选出最后计算结果最低的,作为最终选择性。  

stxndistinct,很好理解,就是字段组合后的唯一值个数。

stxdependencies,当一个字段确定后,另一个字段是唯一值的比例有多少?例如a=1, b={1,2,3,4,5,....}; a=2,b=1,这里只有后面这条算b依赖a。 依赖条数除以总数即a => b的依赖值。可以用于评估两个字段都是等值条件时的选择性。生成stxdependencies的算法很简单,a => b 等于 count(distinct a)/count(distinct a,b);b=>a 等于 count(distinct b)/count(distinct a,b);

postgres=# \d t  
                 Table "public.t"  
 Column |  Type   | Collation | Nullable | Default   
--------+---------+-----------+----------+---------  
 c1     | integer |           |          |   
 c2     | integer |           |          |   
Statistics objects:  
    "public"."s1" (ndistinct, dependencies) ON c1, c2 FROM t  
  
postgres=# select * from pg_statistic_ext where stxname='s2';  
 stxrelid | stxname | stxnamespace | stxowner | stxkeys | stxkind |   stxndistinct   |             stxdependencies                
----------+---------+--------------+----------+---------+---------+------------------+------------------------------------------  
    16394 | s2      |         2200 |       10 | 1 2     | {d,f}   | {"1, 2": 105358} | {"1 => 2": 0.083733, "2 => 1": 0.916200}  
(1 row)  
  
postgres=# select count(distinct c1) from t;  
 count   
-------  
 10000  
(1 row)  
  
postgres=# select count(distinct c2) from t;  
 count    
--------  
 100001  
(1 row)  
  
postgres=# select count(distinct (c1,c2)) from t;  
s count    
--------  
 109999  
(1 row)  
  
postgres=# select 10000/109999.0;  
        ?column?          
------------------------  
 0.09090991736288511714  
(1 row)  
  
postgres=# select 100001/109999.0;  
        ?column?          
------------------------  
 0.90910826462058745989  
(1 row)  
  
postgres=# select 0.083733+0.916200;  
 ?column?   
----------  
 0.999933  
(1 row)  

评估例子

a = ? and b = ? 的选择性   
=   
min( "选择性(a) * (a=>b)" , "选择性(b) * (b=>a)" )  

8、SQL举例

8.1 多个字段条件

postgres=# explain (analyze) select * from tbl where c1=1 and c2='abc';  
                                               QUERY PLAN                                                 
--------------------------------------------------------------------------------------------------------  
 Seq Scan on tbl  (cost=0.00..243458.09 rows=96 width=45) (actual time=802.182..802.182 rows=0 loops=1)  
   Filter: ((c1 = 1) AND (c2 = 'abc'::text))  
   Rows Removed by Filter: 10000000  
 Planning time: 0.098 ms  
 Execution time: 802.203 ms  
(5 rows)  

创建了多字段统计信息后,这两个条件在统计信息之列,所以可以用他们的依赖度来算组合AND条件的选择性。

算法:选择性最低的条件的选择性 * 与另一个字段的依赖度,得到组合选择性。

0.642100 * (148/1e+07) * 1e+07 = 95.0308 (这里反推的选择性有一点失真,大概原理就是这样)  

8.2 多个字段条件求唯一值

postgres=# explain (analyze) select c1,c2,count(*) from tbl group by c1,c2;  
                                                          QUERY PLAN                                                             
-------------------------------------------------------------------------------------------------------------------------------  
 GroupAggregate  (cost=1561215.43..1698692.02 rows=3747653 width=17) (actual time=11632.613..16843.873 rows=5147139 loops=1)  
   Group Key: c1, c2  
   ->  Sort  (cost=1561215.43..1586215.45 rows=10000006 width=9) (actual time=11632.597..14202.457 rows=10000000 loops=1)  
         Sort Key: c1, c2  
         Sort Method: external merge  Disk: 185984kB  
         ->  Seq Scan on tbl  (cost=0.00..193458.06 rows=10000006 width=9) (actual time=0.008..1567.024 rows=10000000 loops=1)  
 Planning time: 0.215 ms  
 Execution time: 17246.889 ms  
(8 rows)  

直接使用了多列统计信息中的唯一值统计信息3747653。

"2, 3": 3747653  

9、如何利用自定义统计信息统计多个字段 唯一值、多列依赖性。

9.1 PostgreSQL已有了单列唯一值的统计,我们可以通过pg_stats.n_distinct以及pg_class.reltuples查询到。

通过create statistic,数据库会自动收集多列值的统计信息,我们查询pg_statistic_ext.stxndistinct,可以得到多列唯一值的估计值。

9.2 多列依赖性指的是列与列之间值的依赖强度,是一个小于等于1的系数。1表示强依赖,

小结

1、PostgreSQL 10支持自定义多列统计信息,目前支持 多列组合唯一值、列与列的相关性。

2、多列唯一值可用于评估group by, count(distinct)等。

3、列与列相关性可用于估算多个列AND条件的选择性。算法

a = ? and b = ? 的选择性   
=   
min( "选择性(a) * (a=>b)" , "选择性(b) * (b=>a)" )  

4、由于多列统计信息的组合很多,因此数据库默认只统计单列的柱状图。当用户意识到某些列会作为组合查询列时,再创建自定义多列统计信息即可。

参考

https://www.postgresql.org/docs/10/static/multivariate-statistics-examples.html

https://www.postgresql.org/docs/10/static/sql-createstatistics.html

https://www.postgresql.org/docs/10/static/planner-stats.html#planner-stats-extended

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
1月前
|
XML Java 数据库连接
WebGIS 信息系统-数据库设计
WebGIS 信息系统-数据库设计
39 0
|
5月前
|
XML 开发框架 前端开发
J2EE之自定义框架知识(下篇 综合运用增删改查)
J2EE之自定义框架知识(下篇 综合运用增删改查)
39 0
|
6月前
|
数据库 OceanBase
OceanBase 数据库的版本信息
OceanBase 数据库的版本信息
920 1
|
2月前
|
存储 监控 安全
内网屏幕监控软件的数据存储与管理:使用SQLite数据库保存监控记录和配置信息
在当今数字化时代,安全和监控在企业和组织中变得至关重要。内网屏幕监控软件作为一种关键工具,帮助组织监视员工的活动并确保信息安全。这种软件不仅需要高效地记录和管理监控数据,还需要能够方便地进行配置和调整。本文将讨论如何使用SQLite数据库来保存监控记录和配置信息,并介绍如何通过自动化机制将监控到的数据提交到指定网站。
149 2
|
1月前
|
SQL 存储 数据管理
数据库系统架构与DBMS功能探微:现代信息时代数据管理的关键
数据库系统架构与DBMS功能探微:现代信息时代数据管理的关键
35 1
|
6月前
|
前端开发 Java 数据库
JSp城市生活信息收集发布网myeclipse开发sqlserver2008数据库BS模式java编程网页结构struts2
JSP城市生活信息收集发布网是一套完善的web设计系统,对理解JSP java编程开发语言有帮助 struts2 dao+bean mvc模式,系统具有完整的源代码和数据库,开发环境为TOMCAT7.0,Myeclipse8.5开发,数据库为sqlserver2008,使用java语言开发,系统主要采用B/S模式开发。
23 0
|
2月前
|
数据库 Android开发 数据库管理
【Android】使用android studio查看内置数据库信息
【Android】使用android studio查看内置数据库信息
64 0
|
7月前
|
Java
jpa实现增删改查,分页,自定义查询,jpql查询
jpa实现增删改查,分页,自定义查询,jpql查询
44 0
|
3月前
|
关系型数据库 MySQL 数据库
『 MySQL数据库 』聚合统计
『 MySQL数据库 』聚合统计
|
3月前
|
存储 关系型数据库 MySQL
认识MySQL数据库中用户自定义变量
认识MySQL数据库中用户自定义变量。
39 0