MySQL 8.0 中統計信息直方圖的嘗試
直方圖是表上某個字段在按照一定百分比和規律采樣後的數據分布的一種描述,最重要的作用之一就是根據查詢條件,預估符合條件的數據量,為sql執行計劃的生成提供重要的依據
在MySQL 8.0之前的版本中,MySQL僅有一個簡單的統計信息卻沒有直方圖,沒有直方圖的統計信息可以說是沒有任何意義的。
MySQL 8.0新特性之一就是開始支持統計信息的直方圖,這個概念很早就提出來了,抽空具體嘗試了一下使用方法。
之前寫過MSSQL相關統計信息的一點東西,在原理上都是一致的,https://www.cnblogs.com/wy123/p/5875237.html
照舊,直接上例子,造數據,創建一個測試環境
createtable 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下面,還沒有直方圖的概念https://www.cnblogs.com/wy123/p/6561517.html
觸發統計信息更新的變量還是set global innodb_stats_on_metadata = 1;但是經測試,統計信息的直方圖並沒有因此而更新。
innodb_stats_on_metadata在MySQL5.7中影響到的是MySQL的索引上的統計信息,而這裏純粹是統計信息的直方圖(MySQL 8.0中直方圖跟索引沒有必然的關系)。
另外,這裏經過反復測試發現,buckets的數據量,與生成直方圖的效率並沒有非常明顯的關系,如下截圖,也並不清楚,buckets數量跟取樣百分比有什麽關系。
關於生成直方圖中時的資源的消耗
直方圖的生成是一個比較消耗資源的過程的,如下是在反復測試創建直方圖的過程中,zabbix監控到的服務器的CPU使用情況,當然,這裏僅僅觀察了一下CPU使用率的問題。
因此,直方圖再好,真要大規模應用的使用,還是要綜合考量的,在什麽時候執行更新,以及怎麽去觸發它的更新。
這裏僅僅是粗淺嘗試,難免有很多認識不足的地方。
參考:https://mysqlserverteam.com/histogram-statistics-in-mysql/
MySQL 8.0 中統計信息直方圖的嘗試