阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

MySQL 8.0 中统计信息直方图的尝试

194次阅读
没有评论

共计 5282 个字符,预计需要花费 14 分钟才能阅读完成。

直方图是表上某个字段在按照一定百分比和规律采样后的数据分布的一种描述,最重要的作用之一就是根据查询条件,预估符合条件的数据量,为 sql 执行计划的生成提供重要的依据

在 MySQL 8.0 之前的版本中,MySQL 仅有一个简单的统计信息却没有直方图,没有直方图的统计信息可以说是没有任何意义的。

MySQL 8.0 新特性之一就是开始支持统计信息的直方图,这个概念很早就提出来了,抽空具体尝试了一下使用方法。

照旧,直接上例子,造数据,创建一个测试环境

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 格式的字符串,可读性并不好。

MySQL 8.0 中统计信息直方图的尝试

想到了 sqlserver 中 DBCC SHOW_STATISTICS 的直方图信息,如下的格式,直方图中的数据分布情况看起来非常清晰直观

MySQL 8.0 中统计信息直方图的尝试

于是就做了一个 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]

很明显,
1,”2018-06-15 04:57:48.000000″ 和 ”2018-07-02 15:13:04.000000″ 是类似于 sqlserver 中直方图中的下限值与上限值
2,0.25 小于 bucket 的值的比例(也就小于这个区间上限制值的比例)
3,95311 是这个区间的字段值不重复的行数。
到最后一个 bucket,采样率必然是 1,也就是 100%

MySQL 8.0 中统计信息直方图的尝试

需要注意的是,直方图的更新时间是标准时间(UTC value),而不是服务器当前时间。
MySQL 8.0 中的直方图基本上与 sqlserver 的直方图一致,都是基于单列的抽样预估,但是 MySQL 直方图中没有类似于 sqlserver 中的字段选择性,
不过这个字段选择性本身意义也不大,sqlserver 中对于复合索引,两个字段合计在一块统计,除非两个字段的同时分布的都很均匀,否则多字段索引的字段选择性参考意义不大。
这也是复合索引无法做到较为精确预估的原因。

存在的疑问?

写过一点 MySQL 统计信息的,不过是在 MySQL5.7 下面,还没有直方图的概念 https://www.linuxidc.com/Linux/2018-08/153704.htm
触发统计信息更新的变量还是 set global innodb_stats_on_metadata = 1; 但是经测试,统计信息的直方图并没有因此而更新。
innodb_stats_on_metadata 在 MySQL5.7 中影响到的是 MySQL 的索引上的统计信息,而这里纯粹是统计信息的直方图(MySQL 8.0 中直方图跟索引没有必然的关系)。
另外,这里经过反复测试发现,buckets 的数据量,与生成直方图的效率并没有非常明显的关系,如下截图,也并不清楚,buckets 数量跟取样百分比有什么关系。

MySQL 8.0 中统计信息直方图的尝试

又仔细看了一下参考链接的内容,发现这么一段话:

  1. Maintaining an index has a cost. If you have an index, every INSERT/UPDATE/DELETE causes the index to be updated. This is not free, and will have an impact on your performance. A histogram on the other hand is created once and never updated unless you explicitly ask for it. It will thus not hurt your INSERT/UPDATE/DELETE-performance.

  它本身是说明索引与直方图之间的关系的,提到直方图创建之后并不会自动更新,除非主动更新。

不得不吐槽的就是,如果我在某个字段上创建了一个索引,还需要顺便在创建一个统计信息直方图?并且这个直方图并不会随着数据的变化自动更新,还需要手动更新。
MySQL 8.0 中会不会把统计信息和索引关联起来,或者根据需要自动创建统计信息,如果统计信息做不到自动更新,基本上可以认为是残废的统计信息了。

关于生成直方图中时的资源的消耗

直方图的生成是一个比较消耗资源的过程的,如下是在反复测试创建直方图的过程中,zabbix 监控到的服务器的 CPU 使用情况,当然,这里仅仅观察了一下 CPU 使用率的问题。
因此,直方图再好,真要大规模应用的使用,还是要综合考量的,在什么时候执行更新,以及怎么去触发它的更新。

MySQL 8.0 中统计信息直方图的尝试

这里仅仅是粗浅尝试,难免有很多认识不足的地方。

一些有意思的东西

本文最后给出的参考链接中发现一些有意思的东西
MySQL 8.0 中一些有意思的预估算法,看来看去,跟 sqlserver 中的差别不大,都是类似大概这几种算法,算是没有办法的办法了。
对于两个谓词结合在一起时候的预估,或者是没有统计信息覆盖的预估,基本上可以认为是瞎蒙的,因此上文中也提到,多个谓词结合起来的���择性,没有什么意义。

————————————
AND      : P(A and B) = P(A) * P(B)
OR        : P(A or B)  = P(A) + P(B) – P(A and B)
=        : 1/10
<,>      : 1/3
BETWEEN  : 1/4
IN (list) : MIN(#items_in_list * SEL(=), 1/2)
IN subq  : [1]
NOT OP    : 1-SEL(OP)

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2022-01-22发表,共计5282字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中