MySQL 8.0 中统计信息直方图的尝试
直方图是表上某个字段在按照一定百分比和规律采样后的数据分布的一种描述,最重要的作用之一就是根据查询条件,预估符合条件的数据量,为sql执行计划的生成提供重要的依据
在mysql 8.0之前的版本中,mysql仅有一个简单的统计信息却没有直方图,没有直方图的统计信息可以说是没有任何意义的。
mysql 8.0新特性之一就是开始支持统计信息的直方图,这个概念很早就提出来了,抽空具体尝试了一下使用方法。
之前写过mssql相关统计信息的一点东西,在原理上都是一致的,
照旧,直接上例子,造数据,创建一个测试环境
create table test ( id int auto_increment primary key, name varchar(100), create_date datetime , index (create_date desc) ); use `db01`$$ drop procedure if exists `insert_test_data`$$ create definer=`root`@`%` procedure `insert_test_data`() begin declare v_loop int; set v_loop = 100000; while v_loop>0 do insert into test(name,create_date)values (uuid(),date_add(now(),interval -rand()*100000 minute) ); set v_loop = v_loop - 1; end while; end$$ delimiter ;
mysql中统计信息的创建,不同于mssql,mysql统计信息不依赖于索引,需要单独创建,语法如下
--创建字段上的统计直方图信息
analyze table test update histogram on create_date,name with 16 buckets;
--删除字段上的统计直方图信息
analyze table test drop histogram on create_date
1,可以一次性创建多个字段的统计信息,系统会逐个创建列出的字段上的统计信息,统计信息不依赖于索引,这一点与mssql不同(当然mssql也可以抛开索引独立创建统计信息)
2,buckets值是一个必须提供的参数,默认值为1000,范围是1-1024,这一点也不同与mssql也不一样,mssql是有一个类似的最大值为200的步长(step)字段
3,一般来说,数据量较大的情况下,对于不重复或者重复性不高的数据,buckets值越大,描述出来的统计信息越详细
4,统计信息的具体内容在 information_schema.column_statistics中,但是可读性并不好,可以根据需求自行解析(出来一种自己喜欢的格式)
与sqlserver中的统计信息一样,理论上,在准确性与取样百分比(buckets)是成正比的,当然生成统计信息的代价也就越大,
至于buckets与统计信息的取样百分比,以及综合代价,笔者暂时没有找到相关的资料。
如下是通过analyze table test update histogram on create_date with 4 buckets;创建的统计信息直方图
可以发现直方图的histogram字段是一个json格式的字符串,可读性并不好。
想到了sqlserver中dbcc show_statistics的直方图信息,如下的格式,直方图中的数据分布情况看起来非常清晰直观
于是就做了一个mysql直方图的格式转换,说白了就是解析information_schema.column_statistics表中的histogram 字段中的json内容
如下,一个简单的解析直方图统计信息json数据的存储过程
delimiter $$ use `db01`$$ drop procedure if exists `parse_column_statistics`$$ create definer=`root`@`%` procedure `parse_column_statistics`( in `p_schema_name` varchar(200), in `p_table_name` varchar(200), in `p_column_name` varchar(200) ) begin declare v_histogram text; -- get the special histogram select histogram->>'$."buckets"' into v_histogram from information_schema.column_statistics where schema_name = p_schema_name and table_name = p_table_name and column_name = p_column_name; -- remove the first and last [ and ] char set v_histogram = substring(v_histogram,2,length(v_histogram)-2);
drop table if exists t_buckets ; create temporary table t_buckets ( id int auto_increment primary key, buckets_content varchar(500) ); -- split by "]," and get single bucket content while (instr(v_histogram,'],')>0) do insert into t_buckets(buckets_content) select substring(v_histogram,1,instr(v_histogram,'],')); set v_histogram = substring(v_histogram,instr(v_histogram,'],')+2,length(v_histogram)); end while;
insert into t_buckets(buckets_content) select v_histogram; -- get the basic statistics data with cte as ( select histogram->>'$."last-updated"' as last_updated, histogram->>'$."number-of-buckets-specified"' as number_of_buckets_specified from information_schema.column_statistics where schema_name = p_schema_name and table_name = p_table_name and column_name = p_column_name ) select case when id = 1 then p_schema_name else '' end as schema_name, case when id = 1 then p_table_name else '' end as table_name, case when id = 1 then p_column_name else '' end as column_name, case when id = 1 then last_updated else '' end as last_updated, case when id = 1 then number_of_buckets_specified else '' end as 'number_of_buckets_specified' , id as buckets_specified_index, buckets_content from ( select * from cte,t_buckets )t; end$$ delimiter ;
于是,第一个截图中的结果就转换为了如下的格式
这里刻意按照4个buckets生成的直方图,应该来说足够简单了,熟悉mssql直方图同学,应该一眼就可以看明白这个直方图的含义(测试数据量是400,000)
以第一个bucket为例,["2018-06-15 04:57:48.000000", "2018-07-02 15:13:04.000000", 0.25, 95311]
很明显,"2018-06-15 04:57:48.000000"和"2018-07-02 15:13:04.000000"是类似于sqlserver中直方图中的下限值与上限值,0.25是直方图的采样率25%,95311没猜错的话应该是这个区间的行数。
到最后一个bucket,采样率必然是1,也就是100%
需要注意的是,直方图的更新时间是标准时间,而不是服务器当前时间。
需要注意的是,mysql 8.0中的直方图基本上与sqlserver的直方图一致,都是基于单列的抽样预估,但是mysql直方图中没有类似于sqlserver中的字段选择性,
不过这个字段选择性本身意义也不大 ,sqlserver中对于复合索引,两个字段合计在一块统计,除非两个字段的同时分布的都很均匀,否则多字段索引的字段选择性参考意义不大。
这也是符合索引无法做到较为精确预估的原因。
存在的疑问?
之前写过一点mysql统计信息的,不过是在mysql5.7下面,还没有直方图的概念
触发统计信息更新的变量还是set global innodb_stats_on_metadata = 1;但是经测试,统计信息的直方图并没有因此而更新。
innodb_stats_on_metadata在mysql5.7中影响到的是mysql的索引上的统计信息,而这里纯粹是统计信息的直方图(mysql 8.0中直方图跟索引没有必然的关系)。
另外,这里经过反复测试发现,buckets的数据量,与生成直方图的效率并没有非常明显的关系,如下截图,也并不清楚,buckets数量跟取样百分比有什么关系。
关于生成直方图中时的资源的消耗
直方图的生成是一个比较消耗资源的过程的,如下是在反复测试创建直方图的过程中,zabbix监控到的服务器的cpu使用情况,当然,这里仅仅观察了一下cpu使用率的问题。
因此,直方图再好,真要大规模应用的使用,还是要综合考量的,在什么时候执行更新,以及怎么去触发它的更新。
这里仅仅是粗浅尝试,难免有很多认识不足的地方。
参考: