浅析MySQL 8.0直方图原理

mysql · 浏览次数 : 0

小编点评

**分析结果** | 阶段 | 指令数量 | 耗时 | |---|---|---| | Index scan on t1 using PRIMARY (reverse) | 8828 | 0.326 | | Index lookup on t2 using idx_order_id | 1 | 0.0025 | | Index range scan on t1 using idx_sys_id_create_time | 338 | 0.0025 | | Index lookup on t2 using idx_order_id | 1 | 0.0025 | | Total | 49 | 0.11 | **优化计划** 1. 使用索引进行排序和查找。 2. 使用索引进行范围扫描和索引扫描。 3. 使用索引进行嵌套 left join。 **收益** 通过优化查询计划,可以显著提高查询性能,从 0.11 秒缩短到 0.01 秒。

正文

本文分享自华为云社区《【MySQL技术专栏】MySQL8.0直方图介绍》,作者:GaussDB 数据库。

背景

数据库查询优化器负责将SQL查询转换为尽可能高效的执行计划,但因为数据环境不断变化导致优化器对查询数据了解的不够充足,可能无法生成最优的执行计划进而影响查询效率,因此MySQL8.0推出了直方图(histogram)功能来解决该问题。

直方图用于统计字段值的分布情况,向优化器提供统计信息。利用直方图,可以对一张表的一列数据做分布统计,估算where条件中过滤字段的选择率,从而帮助优化器更准确地估计查询过程中的行数,选择更高效的查询计划。

本文将对直方图概念进行介绍,借助举例描述直方图的使用方式,对创建/删除直方图的原理进行浅析,并通过例子说明其应用场景。

MySQL8.0直方图介绍

数据库中,查询优化器所生成执行计划的好坏关乎执行耗时的多少,优化器若是不清楚表中数据的分布情况,可能会导致无法生成最优的执行计划,造成执行时浪费时间。

假设一条SQL语句要查询相等间隔的两个不同时间段内出行的人数,若不知道每个时间段内的人数,优化器会假设人数在两个不同时间段内是均匀分布的。如果两个时间段内人数相差较大,这样优化器估算的统计数据就出现严重偏差,从而可能选择错误的执行计划。那么,如何使优化器比较清楚地知道数据统计情况进而生成好的执行计划呢?

一种解决方法就是,在列上建立直方图,从而近似地获取一列上的数据分布情况。利用好直方图,将会带来很多方面收益:

(1)查询优化:提供关于数据分布的统计信息,帮助优化查询计划,选择合适的索引和优化查询语句,从而提高查询性能;

(2)索引设计:通过分析数据的分布情况,帮助确定哪些列适合创建索引,以提高查询效率;

(3)数据分析:提供数据的分布情况,帮助用户了解数据的特征和趋势。

直方图分为两类:等宽直方图(singleton)和等高直方图(equi-height)。等宽直方图是每个桶保存一个值以及这个值累积频率:

SCHEMA_NAME: xxx//库名

TABLE_NAME: xxx//表名

COLUMN_NAME: xxx//列名

HISTOGRAM: {

"buckets":[

[

xxx, //桶中数值

xxx //取值频率

],

......

],

"data-type":"xxx", //数据类型

"null-values":xxx, //是否有NULL值

"collation-id":xxx,

"last-updated":"xxxx-xx-xx xx:xx:xx.xxxxxx", //更新时间

"sampling-rate":xxx, //采样率,1表示采集所有数据

"histogram-type":"singleton", //桶类型,等宽

"number-of-buckets-specified":xxx //桶数量

}

等高直方图每个桶需要保存不同值的个数,上下限以及累积频率等:

SCHEMA_NAME: xxx

TABLE_NAME: xxx

COLUMN_NAME: xxx

HISTOGRAM: {

"buckets":[

[

xxx, //最小值

xxx, //最大值

xxx, //桶值出现的频率

xxx //桶值出现的次数

],

......

],

"data-type":"xxx",

"null-values":xxx,

"collation-id":xxx,

"last-updated":"xxxx-xx-xx xx:xx:xx.xxxxxx",

"sampling-rate":xxx,

"histogram-type":"equi-height", //桶类型,等高

"number-of-buckets-specified":xxx

}

MySQL8.0直方图使用方式

创建和删除直方图时涉及analyze语句,常用语法格式为:

创建直方图:

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] ...

 

具体示例:

mysql> create table t1(c1 int,c2 int,c3 int,c4 int,c5 int,c6 int,c7 int,c8 int,c9 int,c10 int,c11 int,c12 int,c13 datetime,c14 int,c15 int,c16 int,primary key(c1));

Query OK, 0 rows affected (0.01 sec)

mysql> insert into t1 values(1,2,3,4,5,6,7,8,9,10,11,12,'0000-01-01',14,15,16),(2,2,3,4,5,6,7,8,9,10,11,12,'0500-01-01',14,15,16),(3,2,3,4,5,6,7,8,9,10,11,12,'1000-01-01',14,15,16),(4,2,3,4,5,6,7,8,9,10,11,12,'1500-01-01',14,15,16),(5,2,3,4,5,6,7,8,9,10,11,12,'1500-01-01',14,15,16);

Query OK, 5 rows affected (0.00 sec)

Records: 5 Duplicates: 0 Warnings: 0

 

创建直方图:

mysql> analyze table t1 update histogram on c13;

+---------+-----------+----------+------------------------------------------------+

| Table | Op | Msg_type | Msg_text |

+---------+-----------+----------+------------------------------------------------+

| test.t1 | histogram | status | Histogram statistics created for column 'c13'. |

+---------+-----------+----------+------------------------------------------------+

1 row in set (0.01 sec)

 

查看直方图信息:

mysql> select json_pretty(histogram)result from information_schema.column_statistics where table_name = 't1' and column_name = 'c13'\G

*************************** 1. row ***************************

result: {

"buckets": [

[

"0000-01-01 00:00:00.000000", //统计的列值

0.2 //统计的相对频率,下同

],

[

"0500-01-01 00:00:00.000000",

0.4

],

[

"1000-01-01 00:00:00.000000",

0.6

],

[

"1500-01-01 00:00:00.000000",

1.0

]

],

"data-type": "datetime", //统计的数据类型

"null-values": 0.0, //NULL值的比例

"collation-id": 8, //直方图数据的排序规则ID

"last-updated": "2023-09-30 16:05:28.533732", //最近更新直方图的时间

"sampling-rate": 1.0, //直方图构建采样率

"histogram-type": "singleton", //直方图类型,等宽

"number-of-buckets-specified": 100 //桶数量

}

1 row in set (0.00 sec)

 

删除直方图:

mysql> analyze table t1 drop histogram on c13;

+---------+-----------+----------+------------------------------------------------+

| Table | Op | Msg_type | Msg_text |

+---------+-----------+----------+------------------------------------------------+

| test.t1 | histogram | status | Histogram statistics removed for column 'c13'. |

+---------+-----------+----------+------------------------------------------------+

1 row in set (0.00 sec)

 

MySQL8.0直方图原理浅析

直方图原理整体框架可概括为下图所示:

 

直方图代码主要包含在sql/histograms路径下,带有equi_height前缀的相关文件涉及等高直方图,带有singleton前缀的相关文件涉及等宽直方图,带有value_map前缀的相关文件涉及保存统计值结构,histogram.h/histogram.cc涉及直方图相关调用接口。

Sql_cmd_analyze_table::handle_histogram_command为对直方图操作的整体处理入口,目前只支持在一张表上进行直方图相关操作。创建直方图的主要调用堆栈如下所示,update_histogram为创建直方图的入口。

mysql_execute_command

->Sql_cmd_analyze_table::execute

->Sql_cmd_analyze_table::handle_histogram_command

->Sql_cmd_analyze_table::update_histogram

->histograms::update_histogram

->prepare_value_maps

->fill_value_maps

->build_histogram

->store_histogram

->dd::cache::Dictionary_client::update

->dd::cache::Storage_adapter::store

->dd::Column_statistics_impl::store_attributes

->histograms::Singleton<xxx>::histogram_to_json

对于创建流程展开描述,prepare_value_maps中主要根据直方图列类型创建对应的value_map做准备,之后利用histogram_generation_max_mem_size参数值(限制生成直方图时所允许使用的最大内存大小)和单行数据大小计算后控制统计采样率,fill_value_maps将反复读取数据填充到对应类型的value_map中,key为列实际值,value为其出现的次数。调用build_histogram以完成对直方图的构建,如果桶个数(num_buckets)比不同值个数(value_map.size())要大,则自动创建一个等宽直方图,否则创建一个等高直方图。两种直方图的创建逻辑分别在Singleton<T>:: build_histogram和Equi_height<T>:: build_histogram中。

构建直方图完成后调用store_histogram,将结果以JSON的形式存储在系统表中,通过INFORMATION_SCHEMA.COLUMN_STATISTICS对用户呈现,histogram_to_json会将直方图结果转换为Json_object格式,例如last-updated使用Json_datetime格式保存、histogram-type使用Json_string格式保存、sampling rate使用Json_double格式保存等,再依次调用json_object->add_clone将各json类型字段保存。

删除直方图的主要堆栈如下所示。drop_histograms逻辑中在删除直方图前会先尝试获取以检查对应直方图是否真的存在,不存在的话就提前终止逻辑,存在则删除。

mysql_execute_command

->Sql_cmd_analyze_table::execute

->Sql_cmd_analyze_table::handle_histogram_command

->Sql_cmd_analyze_table::update_histogram

->histograms::update_histogram

MySQL8.0直方图优化场景

优化方面,如本文在前所描述的直方图作用,利用直方图信息估算where条件中各谓词的选择率,帮助选择最优的执行计划。例如,表存在如下所示数据倾斜场景。

mysql> select sys_id,order_status,count(*) from my_table_1 group by sys_id,order_status order by 1,2,3;

+--------+--------------+----------+

| sys_id | order_status | count(*) |

+--------+--------------+----------+

| 3 | 1 | 1 |

| 3 | 2 | 200766 |

| 3 | 3 | 3353 |

| 3 | 4 | 1325 |

| 5 | 1 | 13 |

| 5 | 2 | 2478373 |

| 5 | 3 | 43243 |

| 5 | 4 | 13529 |

| 6 | 2 | 171388 |

| 6 | 3 | 254 |

| 6 | 4 | 716 |

+--------+--------------+----------+

执行如下SQL语句时,因为存在数据倾斜而优化器未能准确估计导致执行计划选择错误,执行耗时约为1.35s。

mysql> explain analyze select t1.id, t1.order_number, t1.create_time, t1.order_status from my_table_1 t1 left join my_table_2 t2 on t1.id = t2.order_id WHERE t1.sys_id = 5 and t1.order_status in (1) and t1.create_time >= '2022-09-10 00:00:00' and t1.create_time <= '2022-09-16 23:59:59' order by t1.id desc LIMIT 20\G

*************************** 1. row ***************************

EXPLAIN: -> Limit: 20 row(s) (cost=4163.10 rows=20) (actual time=1350.825..1350.825 rows=0 loops=1)

-> Nested loop left join (cost=4163.10 rows=49) (actual time=1350.825..1350.825 rows=0 loops=1)

-> Filter: ((t1.order_status = 1) and (t1.sys_id = 5) and (t1.create_time >= TIMESTAMP'2022-09-10 00:00:00') and (t1.create_time <= TIMESTAMP'2022-09-16 23:59:59')) (cost=215.79 rows=49) (actual time=1350.823..1350.823 rows=0 loops=1)

-> Index scan on t1 using PRIMARY (reverse) (cost=215.79 rows=8828) (actual time=0.088..1209.201 rows=2910194 loops=1)

-> Index lookup on t2 using idx_order_id (order_id=t1.id) (cost=0.63 rows=1) (never executed)

 

通过执行ANALYZE table my_table_1 UPDATE HISTOGRAM ON order_status, sys_id, create_time语句创建直方图后,再次执行上述SQL语句时,执行计划中的索引发生了变化,执行耗时为0.11s。因此可以看出,优化器利用更准确的数据分布信息选择了更优的执行计划。

mysql> explain analyze select t1.id, t1.order_number, t1.create_time, t1.order_status from my_table_1 t1 left join my_table_2 t2 on t1.id = t2.order_id WHERE t1.sys_id = 5 and t1.order_status in (1) and t1.create_time >= '2022-09-10 00:00:00' and t1.create_time <= '2022-09-16 23:59:59' order by t1.id desc LIMIT 20\G

*************************** 1. row ***************************

EXPLAIN: -> Limit: 20 row(s) (cost=38385.46 rows=20) (actual time=114.217..114.217 rows=0 loops=1)

-> Nested loop left join (cost=38385.46 rows=62764) (actual time=114.216..114.216 rows=0 loops=1)

-> Sort: t1.id DESC, limit input to 20 row(s) per chunk (cost=28200.86 rows=62668) (actual time=114.215..114.215 rows=0 loops=1)

-> Filter: (t1.order_status = 1) (cost=28200.86 rows=62668) (actual time=114.207..114.207 rows=0 loops=1)

-> Index range scan on t1 using idx_sys_id_create_time, with index condition: ((t1.sys_id = 5) and (t1.create_time >= TIMESTAMP'2022-09-10 00:00:00') and (t1.create_time <= TIMESTAMP'2022-09-16 23:59:59')) (cost=28200.86 rows=62668) (actual time=0.326..112.912 rows=31142 loops=1)

-> Index lookup on t2 using idx_order_id (order_id=t1.id) (cost=0.62 rows=1) (never executed)

另外,当where条件中变量值不同时,优化器也根据数据分布情况选择了准确的执行计划,使得执行效率提高。

mysql> explain format=tree select t1.id, t1.order_number, t1.create_time, t1.order_status from my_table_1 t1 left join my_table_2 t2 on t1.id = t2.order_id WHERE t1.sys_id = 5 and t1.order_status in (2) and t1.create_time >= '2020-10-01 00:00:00' and t1.create_time <= '2020-10-09 23:59:59' order by t1.id desc LIMIT 20\G

*************************** 1. row ***************************

EXPLAIN: -> Limit: 20 row(s) (cost=13541.27 rows=20)

-> Nested loop left join (cost=13541.27 rows=44)

-> Filter: ((t1.order_status = 2) and (t1.sys_id = 5) and (t1.create_time >= TIMESTAMP'2020-10-01 00:00:00') and (t1.create_time <= TIMESTAMP'2020-10-09 23:59:59')) (cost=15.79 rows=44)

-> Index scan on t1 using PRIMARY (reverse) (cost=15.79 rows=338)

-> Index lookup on t2 using idx_order_id (order_id=t1.id) (cost=0.25 rows=1)

1 row in set (0.00 sec)

 

mysql> explain format=tree select t1.id, t1.order_number, t1.create_time, t1.order_status from my_table_1 t1 left join my_table_2 t2 on t1.id = t2.order_id WHERE t1.sys_id = 5 and t1.order_status in (4) and t1.create_time >= '2020-10-01 00:00:00' and t1.create_time <= '2020-10-09 23:59:59' order by t1.id desc LIMIT 20\G

*************************** 1. row ***************************

EXPLAIN: -> Limit: 20 row(s) (cost=30559.31 rows=20)

-> Nested loop left join (cost=30559.31 rows=55852)

-> Sort: t1.id DESC, limit input to 20 row(s) per chunk (cost=24966.26 rows=55480)

-> Filter: (t1.order_status = 4) (cost=24966.26 rows=55480)

-> Index range scan on t1 using idx_sys_id_create_time, with index condition: ((t1.sys_id = 5) and (t1.create_time >= TIMESTAMP'2020-10-01 00:00:00') and (t1.create_time <= TIMESTAMP'2020-10-09 23:59:59')) (cost=24966.26 rows=55480)

-> Index lookup on t2 using idx_order_id (order_id=t1.id) (cost=0.25 rows=1)

1 row in set (0.00 sec)

所以,通过所提供的统计信息,帮助优化查询计划进而提高查询性能是如前所述应用直方图的一个收益点。

点击关注,第一时间了解华为云新鲜技术~

 

与浅析MySQL 8.0直方图原理相似的内容:

浅析MySQL 8.0直方图原理

本文将对直方图概念进行介绍,借助举例描述直方图的使用方式,对创建/删除直方图的原理进行浅析,并通过例子说明其应用场景。

MySQL bit类型增加索引后查询结果不正确案例浅析

昨天同事遇到的一个案例,这里简单描述一下:一个表里面有一个bit类型的字段,同事在优化相关SQL的过程中,给这个表的bit类型的字段新增了一个索引,然后测试验证 时,居然发现SQL语句执行结果跟不加索引不一样。加了索引后,SQL语句没有查询出一条记录,删除索引后,SQL语句就能查询出几十条记录。下面

万字长文浅析配置对MySQL服务器的影响

有很多的服务器选项会影响这MySQL服务器的性能,比如内存中临时表的大小、排序缓冲区等。有些针对特定存储引擎(如InnoDB)的选项,也会对查询优化很有用。

MySQL性能优化浅析及线上案例

关于数据库的性能优化其实是一个很复杂的大课题,很难通过一篇帖子讲的很全面和深刻,这也就是为什么我的标题是‘浅析’,程序员的成长一定是要付出代价和成本,因为只有真的在一线切身体会到当时的紧张和压力,对于一件事情才能印象深刻,但反之也不能太过于强调代价,如果可以通过一些别人的分享就可以规避一些自己业务的问题和错误的代价也是好的。

分布式数据库 Join 查询设计与实现浅析

本文记录 Mysql 分库分表 和 Elasticsearch Join 查询的实现思路,了解分布式场景数据处理的设计方案。文章从常用的关系型数据库 MySQL 的分库分表Join 分析,再到非关系型 ElasticSearch 来分析 Join 实现策略。逐步深入Join 的实现机制。

浅谈Mysql读写分离的坑以及应对的方案

本文简单介绍了读写分离架构,和出现主从延迟后,如果我们用的读写分离的架构,那么我们应该怎么处理这种情况,相信在日常我们的主从还是或多或少的存在延迟。上面介绍的几种方案,有些方案看上去十分不靠谱,有些方案做了一些妥协,但是都有实际的应用场景,需要我们根据自身的业务情况,合理选择对应的方案。

浅析Vite本地构建原理

前言 随着Vue3的逐渐普及以及Vite的逐渐成熟,我们有必要来了解一下关于vite的本地构建原理。 对于webpack打包的核心流程是通过分析JS文件中引用关系,通过递归得到整个项目的依赖关系,并且对于非JS类型的资源,通过调用对应的loader将其打包编译生成JS 代码,最后再启动开发服务器。

[转帖]浅析Nginx配置获取客户端真实IP的proxy_set_header、X-Real-IP、$remote_addr、X-Forwarded-For、$proxy_add_x_forwarded_for分别是什么意思

https://www.cnblogs.com/goloving/p/15588668.html 一、问题背景 在实际应用中,我们可能需要获取用户的ip地址,比如做异地登陆的判断,或者统计ip访问次数等,通常情况下我们使用 request.getRemoteAddr() 就可以获取到客户端ip,但是

[转帖]浅析IP地址及localhost、127.0.0.1和0.0.0.0的区别

https://www.cnblogs.com/goloving/p/7202151.html 127.0.0.1和0.0.0.0这两个IP地址再熟悉不过了,看起来好像就那么回事,但真正较起真来,这两个IP地址到底有什么作用以及到底有什么不同?貌似谁可以轻松回答,但张嘴却又不知从何说起(这要是面试,

[转帖]浅析nginx的server及server_name的意义详解

https://www.cnblogs.com/goloving/p/7010713.html 一、server_name 详解 当Nginx接到请求后,会匹配其配置中的server模块。匹配方法就是靠请求携带的host和port正好对应其配置中的server_name 和listen。如果做过ip