暂无图片
暂无图片
4
暂无图片
暂无图片
2
暂无图片

Mysql 统计信息之列的直方图

原创 大表哥 2022-03-02
2101

image.png
熟悉ORACLE的同学可能都知道列的直方图的概念: 简单地说就是记录了列中数值的分布情况的信息 。
ORACLE 一般常见的直方图有3种: frequency (高频率统计), height balanced(高度均衡直方图), hybrid(混合直方图)
ORACLE 11G 的 ACS 特性,就是直方图为基础信息来实现的执行计划的自由切换。

直方图的信息可以作为统计信息重要的一部分为数据库的 optimizer 生成更好的执行计划 提供了有力的支持。

MYSQL 大叔 是从8.0版本开始的(官方版本8.0.3),引入的列的直方图的信息, 从而来帮助优化器来选择相对更好的执行计划:

mysql的直方图目前有2种形式:

* singleton   
* equi-height   
复制

Singleton:
特点是每个值一个bucket,
每个bucket 保存的信息为: 列的值和该值出现的频率
使用场景为:等值查询和范围查询。

示例图:

Image.png

Equi-height:
特点是每个桶里面会保存多个列的值
每个bucket 保存的信息为: 桶中列的最大值和最小值,累计出现该值的频率, NDV (number of distinct value)等信息
使用场景为:范围查询。

示例图:

Image.png

上面都是一些纯理论上的灌输, 那么我们在实际的数据库表中,什么样的列需要收集直方图的信息呢?
答案大家都知道: 那就是数据分布不均匀的列(skewed & unevent column ),或者从优化器的角度说是 cardinality 小的列。

说了这么多,大表哥来亲自实操感受一下:
大表哥之前做过美丽国的开发项目是关于幼儿园的考勤系统的,美丽国的孩子们还是挺幸福的。。。。
里面有一张核心的表叫考勤表: t_trx_attendance 里面有关于小朋友上学和下学的时间字段: attend_start_time 和attend_end_time.
大家按照常识都知道 一般孩子们的上学时间都是 集中在 7:00 AM -09:00 AM, 放学都是集中在 05:00 PM- 07:00 PM.
当然也会存在 上学迟到,早退的现象。所以在时间字段上 attend_start_time 和attend_end_time 存在数据的倾斜。

下面我们简单的创建一下这个表:

mysql> create table t_trx_attendance ( -> id int not null primary key, -> name varchar(80) not null, -> attend_start_time datetime , -> attend_end_time datetime -> ); Query OK, 0 rows affected (0.01 sec)
复制

我们写一个脚本load 进去1万条的常规的数据 时间都是 入学: 2022-01-01 08:00:00 放学 :2022-01-01 18:00:00

load_data.sh

for i in {1..10000} do /opt/mysql/product/percona8.0/bin/mysql --login-path=root3021 -e "insert into testdb.t_trx_attendance(id,name,attend_start_time,attend_end_time) values($i,'小朋友$i','2022-01-01 08:00:00','2022-01-01 18:00:00');" done
复制

我们查看数据:

mysql> select count(1) from t_trx_attendance; +----------+ | count(1) | +----------+ | 10000 | +----------+ 1 row in set (0.00 sec)
复制

这时候有个 叫 狗蛋儿的小朋友 上学 迟到了 早上 11点来的 刚睡醒。
我们手动插入 狗蛋儿同学的这条记录:

mysql> insert into t_trx_attendance values (10001,'狗蛋儿','2022-01-01 11:00:00','2022-01-01 18:00:00'); Query OK, 1 row affected (0.01 sec) mysql> commit; Query OK, 0 rows affected (0.00 sec)
复制

这个时候 校长要查一下 : 今天上学迟到的学生 早上10点之后到校的。

mysql> explain select * from t_trx_attendance where attend_start_time > '2022-01-01 10:00:00'; +----+-------------+------------------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+------------------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | t_trx_attendance | NULL | ALL | NULL | NULL | NULL | NULL | 9977 | 33.33 | Using where | +----+-------------+------------------+------------+------+---------------+------+---------+------+------+----------+-------------+ 1 row in set, 1 warning (0.01 sec)
复制

这个时候我们可以看到优化器给出的信息里面 数据过滤比例是 33.33% .

狗蛋儿同学一个人就把mysql 大叔的优化器搞得凌乱了…

我们尝试收集一下表的统计信息:

mysql> analyze table t_trx_attendance; +-------------------------+---------+----------+----------+ | Table | Op | Msg_type | Msg_text | +-------------------------+---------+----------+----------+ | testdb.t_trx_attendance | analyze | status | OK | +-------------------------+---------+----------+----------+ 1 row in set (0.01 sec) mysql> explain select * from t_trx_attendance where attend_start_time > '2022-01-01 10:00:00'; +----+-------------+------------------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+------------------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | t_trx_attendance | NULL | ALL | NULL | NULL | NULL | NULL | 9976 | 33.33 | Using where | +----+-------------+------------------+------------+------+---------------+------+---------+------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec)
复制

执行计划里面的信息依然是凌乱的。。。

这个时候我们收集一下列的统计信息:
analyze table t_trx_attendance UPDATE HISTOGRAM ON attend_start_time WITH 8 BUCKETS;

这个时候MYSQL大叔的优化器得到了正确的统计信息。 数据过滤率是 0.01%
这个数值和实际的情况是一致的 狗蛋儿是那些孩子们的 万分之一。

mysql> analyze table t_trx_attendance UPDATE HISTOGRAM ON attend_start_time WITH 8 BUCKETS; +-------------------------+-----------+----------+--------------------------------------------------------------+ | Table | Op | Msg_type | Msg_text | +-------------------------+-----------+----------+--------------------------------------------------------------+ | testdb.t_trx_attendance | histogram | status | Histogram statistics created for column 'attend_start_time'. | +-------------------------+-----------+----------+--------------------------------------------------------------+ 1 row in set (0.01 sec) mysql> explain select * from t_trx_attendance where attend_start_time > '2022-01-01 10:00:00'; +----+-------------+------------------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+------------------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | t_trx_attendance | NULL | ALL | NULL | NULL | NULL | NULL | 9976 | 0.01 | Using where | +----+-------------+------------------+------------+------+---------------+------+---------+------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec)
复制

我们也可以用 optimizer trace 看一下: “histogram_selectivity”: 0.0001 是符合收集统计信息后的实际情况的。

mysql> SET OPTIMIZER_TRACE = "enabled=on"; Query OK, 0 rows affected (0.00 sec) mysql> SET OPTIMIZER_TRACE_MAX_MEM_SIZE = 1000000; Query OK, 0 rows affected (0.00 sec) mysql> explain select * from t_trx_attendance where attend_start_time > '2022-01-01 10:00:00'; +----+-------------+------------------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+------------------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | t_trx_attendance | NULL | ALL | NULL | NULL | NULL | NULL | 9976 | 0.01 | Using where | +----+-------------+------------------+------------+------+---------------+------+---------+------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec) mysql> SELECT JSON_EXTRACT(TRACE, "$**.filtering_effect") FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE; +---------------------------------------------------------------------------------------------------------------------------------+ | JSON_EXTRACT(TRACE, "$**.filtering_effect") | +---------------------------------------------------------------------------------------------------------------------------------+ | [[{"condition": "(`t_trx_attendance`.`attend_start_time` > TIMESTAMP'2022-01-01 10:00:00')", "histogram_selectivity": 0.0001}]] | +---------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
复制

关于收集和删除列的统计信息语法如下:

ANALYZE TABLE tbl_name UPDATE HISTOGRAM ON col_name [, col_name] WITH N BUCKETS; ANALYZE TABLE tbl_name DROP HISTOGRAM ON col_name [, col_name];
复制

最后大表哥还要分享几个关于收集列的直方图统计信息的tips:
1) 从业务的角度,考虑收集数据有倾斜的列,(选择率高的,唯一(近似唯一)键值的列,统统不需要收集)
2) N 是 指定 BUCKETS 数量的范围是 1-1024, 默认值是100

ANALYZE TABLE tbl_name UPDATE HISTOGRAM ON col_name [, col_name] WITH N BUCKETS;
复制

3) 收集大表的时候可以设置内存参数: 以免创建磁盘的临时IO访问

SET histogram_generation_max_mem_size = 1000000;
复制

4)列的统计信息是保存在视图 information_schema. column_statistics 中的 histogram 是JSON 格式的

mysql> desc information_schema.column_statistics; +-------------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------------+-------------+------+-----+---------+-------+ | SCHEMA_NAME | varchar(64) | NO | | NULL | | | TABLE_NAME | varchar(64) | NO | | NULL | | | COLUMN_NAME | varchar(64) | NO | | NULL | | | HISTOGRAM | json | NO | | NULL | | +-------------+-------------+------+-----+---------+-------+ 4 rows in set (0.00 sec) mysql> select * from information_schema.column_statistics where SCHEMA_NAME='testdb' AND TABLE_NAME='t_trx_attendance'\G *************************** 1. row *************************** SCHEMA_NAME: testdb TABLE_NAME: t_trx_attendance COLUMN_NAME: attend_start_time HISTOGRAM: {"buckets": [["2022-01-01 08:00:00.000000", 0.9999000099990001], ["2022-01-01 11:00:00.000000", 1.0]], "data-type": "datetime", "null-values": 0.0, "collation-id": 8, "last-updated": "2022-03-02 07:52:02.768589", "sampling-rate": 1.0, "histogram-type": "singleton", "number-of-buckets-specified": 8} 1 row in set (0.00 sec)
复制

我们重点观察一下 HISTOGRAM 这个JSON格式列中的内容 :

HISTOGRAM: { "buckets": [["2022-01-01 08:00:00.000000", 0.9999000099990001], ["2022-01-01 11:00:00.000000", 1.0]], -- 实际创建的2个bucket, [列的值,在桶内占的比例] "data-type": "datetime", --列的类型 "null-values": 0.0, --空值的数量 "collation-id": 8, -- 桶的最大个数 "last-updated": "2022-03-02 07:52:02.768589", --收集直方图统计信息的时间 "sampling-rate": 1.0, --数据采样比例是100% "histogram-type": "singleton", -- singleton 类型的直方图 "number-of-buckets-specified": 8 --手动指定的桶的个数 } 1 row in set (0.00 sec)
复制

5)如果要确保MYSQL大叔的优化器读取到列的统计信息,必须要打开变量optimizer_switch中的condition_fanout_filter=on (这个值默认是打开的)

mysql> SELECT @@optimizer_switch\G *************************** 1. row *************************** @@optimizer_switch: index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=on,duplicateweedout=on,subquery_materialization_cost_based=on,use_index_extensions=on,condition_fanout_filter=on,derived_merge=on,use_invisible_indexes=off,skip_scan=on 1 row in set (0.00 sec)
复制
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论

沈宏
暂无图片
2年前
评论
暂无图片 0
8.0.31版本,同样的脚本,做出来的是0.001 mysql> SELECT JSON_EXTRACT(TRACE, "$**.filtering_effect") FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE; +--------------------------------------------------------------------------------------------------------------------------------+ | JSON_EXTRACT(TRACE, "$**.filtering_effect") | +--------------------------------------------------------------------------------------------------------------------------------+ | [[{"condition": "(`t_trx_attendance`.`attend_start_time` > TIMESTAMP'2022-01-01 10:00:00')", "histogram_selectivity": 0.001}]] | +--------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
2年前
暂无图片 点赞
评论
墨天轮福利君
暂无图片
3年前
评论
暂无图片 0
您好,您的文章已入选合格奖,10墨值奖励已经到账请查收! ❤️我们还会实时派发您的流量收益。
3年前
暂无图片 点赞
评论