在開始演示之前,我們先介紹下兩個概念。
概念壹,數據的可選擇性基數,也就是常說的cardinality值。
查詢優化器在生成各種執行計劃之前,得先從統計信息中取得相關數據,這樣才能估算每步操作所涉及到的記錄數,而這個相關數據就是cardinality。簡單來說,就是每個值在每個字段中的唯壹值分布狀態。
比如表t1有100行記錄,其中壹列為f1。f1中唯壹值的個數可以是100個,也可以是1個,當然也可以是1到100之間的任何壹個數字。這裏唯壹值越的多少,就是這個列的可選擇基數。
那看到這裏我們就明白了,為什麽要在基數高的字段上建立索引,而基數低的的字段建立索引反而沒有全表掃描來的快。當然這個只是壹方面,至於更深入的探討就不在我這篇探討的範圍了。
概念二,關於HINT的使用。
這裏我來說下HINT是什麽,在什麽時候用。
HINT簡單來說就是在某些特定的場景下人工協助MySQL優化器的工作,使她生成最優的執行計劃。壹般來說,優化器的執行計劃都是最優化的,不過在某些特定場景下,執行計劃可能不是最優化。
比如:表t1經過大量的頻繁更新操作,(UPDATE,DELETE,INSERT),cardinality已經很不準確了,這時候剛好執行了壹條SQL,那麽有可能這條SQL的執行計劃就不是最優的。為什麽說有可能呢?
來看下具體演示
譬如,以下兩條SQL,
A:
select * from t1 where f1 = 20;B:
select * from t1 where f1 = 30;如果f1的值剛好頻繁更新的值為30,並且沒有達到MySQL自動更新cardinality值的臨界值或者說用戶設置了手動更新又或者用戶減少了sample page等等,那麽對這兩條語句來說,可能不準確的就是B了。
這裏順帶說下,MySQL提供了自動更新和手動更新表cardinality值的方法,因篇幅有限,需要的可以查閱手冊。
那回到正題上,MySQL 8.0 帶來了幾個HINT,我今天就舉個index_merge的例子。
示例表結構:
mysql> desc t1;+------------+--------------+------+-----+---------+----------------+| Field ?| Type | Null | Key | Default | Extra ?|+------------+--------------+------+-----+---------+----------------+| id | int(11) ?| NO ? | PRI | NULL | auto_increment || rank1 ?| int(11) ?| YES ?| MUL | NULL | || rank2 ?| int(11) ?| YES ?| MUL | NULL | || log_time ? | datetime | YES ?| MUL | NULL | || prefix_uid | varchar(100) | YES ?| | NULL | || desc1 ?| text | YES ?| | NULL | || rank3 ?| int(11) ?| YES ?| MUL | NULL | |+------------+--------------+------+-----+---------+----------------+7 rows in set (0.00 sec)表記錄數:
mysql> select count(*) from t1;+----------+| count(*) |+----------+| 32768 |+----------+1 row in set (0.01 sec)這裏我們兩條經典的SQL:
SQL C:
select * from t1 where rank1 = 1 or rank2 = 2 or rank3 = 2;SQL D:
select * from t1 where rank1 =100 ?and rank2 =100 ?and rank3 =100;表t1實際上在rank1,rank2,rank3三列上分別有壹個二級索引。
那我們來看SQL C的查詢計劃。
顯然,沒有用到任何索引,掃描的行數為32034,cost為3243.65。
mysql> explain ?format=json select * from t1 ?where rank1 =1 or rank2 = 2 or rank3 = 2\G*************************** 1. row ***************************EXPLAIN: { ?"query_block": { "select_id": 1, "cost_info": { ?"query_cost": "3243.65" }, "table": { ?"table_name": "t1", ?"access_type": "ALL", ?"possible_keys": [ "idx_rank1", "idx_rank2", "idx_rank3" ?], ?"rows_examined_per_scan": 32034, ?"rows_produced_per_join": 115, ?"filtered": "0.36", ?"cost_info": { "read_cost": "3232.07", "eval_cost": "11.58", "prefix_cost": "3243.65", "data_read_per_join": "49K" ?}, ?"used_columns": [ "id", "rank1", "rank2", "log_time", "prefix_uid", "desc1", "rank3" ?], ?"attached_condition": "((`ytt`.`t1`.`rank1` = 1) or (`ytt`.`t1`.`rank2` = 2) or (`ytt`.`t1`.`rank3` = 2))" } ?}}1 row in set, 1 warning (0.00 sec)我們加上hint給相同的查詢,再次看看查詢計劃。
這個時候用到了index_merge,union了三個列。掃描的行數為1103,cost為441.09,明顯比之前的快了好幾倍。
mysql> explain ?format=json select /*+ index_merge(t1) */ * from t1 ?where rank1 =1 or rank2 = 2 or rank3 = 2\G*************************** 1. row ***************************EXPLAIN: { ?"query_block": { "select_id": 1, "cost_info": { ?"query_cost": "441.09" }, "table": { ?"table_name": "t1", ?"access_type": "index_merge", ?"possible_keys": [ "idx_rank1", "idx_rank2", "idx_rank3" ?], ?"key": "union(idx_rank1,idx_rank2,idx_rank3)", ?"key_length": "5,5,5", ?"rows_examined_per_scan": 1103, ?"rows_produced_per_join": 1103, ?"filtered": "100.00", ?"cost_info": { "read_cost": "330.79", "eval_cost": "110.30", "prefix_cost": "441.09", "data_read_per_join": "473K" ?}, ?"used_columns": [ "id", "rank1", "rank2", "log_time", "prefix_uid", "desc1", "rank3" ?], ?"attached_condition": "((`ytt`.`t1`.`rank1` = 1) or (`ytt`.`t1`.`rank2` = 2) or (`ytt`.`t1`.`rank3` = 2))" } ?}}1 row in set, 1 warning (0.00 sec)我們再看下SQL D的計劃:
不加HINT,
mysql> explain format=json select * from t1 where rank1 =100 and rank2 =100 and rank3 =100\G*************************** 1. row ***************************EXPLAIN: { ?"query_block": { "select_id": 1, "cost_info": { ?"query_cost": "534.34" }, "table": { ?"table_name": "t1", ?"access_type": "ref", ?"possible_keys": [ "idx_rank1", "idx_rank2", "idx_rank3" ?], ?"key": "idx_rank1", ?"used_key_parts": [ "rank1" ?], ?"key_length": "5", ?"ref": [ "const" ?], ?"rows_examined_per_scan": 555, ?"rows_produced_per_join": 0, ?"filtered": "0.07", ?"cost_info": { "read_cost": "478.84", "eval_cost": "0.04", "prefix_cost": "534.34", "data_read_per_join": "176" ?}, ?"used_columns": [ "id", "rank1", "rank2", "log_time", "prefix_uid", "desc1", "rank3" ?], ?"attached_condition": "((`ytt`.`t1`.`rank3` = 100) and (`ytt`.`t1`.`rank2` = 100))" } ?}}1 row in set, 1 warning (0.00 sec)加了HINT,
mysql> explain format=json select /*+ index_merge(t1)*/ * from t1 where rank1 =100 and rank2 =100 and rank3 =100\G*************************** 1. row ***************************EXPLAIN: { ?"query_block": { "select_id": 1, "cost_info": { ?"query_cost": "5.23" }, "table": { ?"table_name": "t1", ?"access_type": "index_merge", ?"possible_keys": [ "idx_rank1", "idx_rank2", "idx_rank3" ?], ?"key": "intersect(idx_rank1,idx_rank2,idx_rank3)", ?"key_length": "5,5,5", ?"rows_examined_per_scan": 1, ?"rows_produced_per_join": 1, ?"filtered": "100.00", ?"cost_info": { "read_cost": "5.13", "eval_cost": "0.10", "prefix_cost": "5.23", "data_read_per_join": "440" ?}, ?"used_columns": [ "id", "rank1", "rank2", "log_time", "prefix_uid", "desc1", "rank3" ?], ?"attached_condition": "((`ytt`.`t1`.`rank3` = 100) and (`ytt`.`t1`.`rank2` = 100) and (`ytt`.`t1`.`rank1` = 100))" } ?}}1 row in set, 1 warning (0.00 sec)對比下以上兩個,加了HINT的比不加HINT的cost小了100倍。
總結下,就是說表的cardinality值影響這張的查詢計劃,如果這個值沒有正常更新的話,就需要手工加HINT了。相信MySQL未來的版本會帶來更多的HINT。