国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 數(shù)據(jù)庫(kù) > MySQL > 正文

一個(gè)優(yōu)化MySQL查詢操作的具體案例分析

2024-07-24 13:07:04
字體:
供稿:網(wǎng)友

這篇文章主要介紹了一個(gè)優(yōu)化MySQL查詢操作的具體案例分析,主要針對(duì)join字段的使用方面做出調(diào)整,需要的朋友可以參考下

問題描述

一個(gè)用戶反映先線一個(gè)SQL語句執(zhí)行時(shí)間慢得無法接受。SQL語句看上去很簡(jiǎn)單(本文描述中修改了表名和字段名):

SELECT count(*) FROM a JOIN b ON a.`S` = b.`S` WHERE a.`L` > '2014-03-30 00:55:00' AND a.`L` < '2014-03-30 01:00:00' ;

且查詢需要的字段都建了索引,表結(jié)構(gòu)如下:

 

 
  1. CREATE TABLE `a` ( 
  2. `L` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00'
  3. `I` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL
  4. `A` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL
  5. `S` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL
  6. `F` tinyint(4) DEFAULT NULL
  7. `V` varchar(256) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT ''
  8. `N` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL
  9. KEY `IX_L` (`L`), 
  10. KEY `IX_I` (`I`), 
  11. KEY `IX_S` (`S`) 
  12. ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 
  13.  
  14. CREATE TABLE `b` ( 
  15. `R` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00'
  16. `V` varchar(32) DEFAULT NULL
  17. `U` varchar(32) DEFAULT NULL
  18. `C` varchar(16) DEFAULT NULL
  19. `S` varchar(64) DEFAULT NULL
  20. `I` varchar(64) DEFAULT NULL
  21. `E` bigint(32) DEFAULT NULL
  22. `ES` varchar(128) DEFAULT NULL
  23. KEY `IX_R` (`R`), 
  24. KEY `IX_C` (`C`), 
  25. KEY `IX_S` (`S`) 
  26. ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

從語句看,這個(gè)查詢計(jì)劃很自然的,就應(yīng)該是先用a作為驅(qū)動(dòng)表,先后使用 a.L和b.S這兩個(gè)索引。而實(shí)際上explain的結(jié)果卻是:

 

 
  1. +----+-------------+-------+-------+---------------+------+---------+----------+---------+-------------+ 
  2. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 
  3. +----+-------------+-------+-------+---------------+------+---------+----------+---------+-------------+ 
  4. | 1 | SIMPLE | b | index | IX_S | IX_S | 195 | NULL | 1038165 | Using index | 
  5. | 1 | SIMPLE | a | ref | IX_L,IX_S | IX_S | 195 | test.b.S | 1 | Using where | 
  6. +----+-------------+-------+-------+---------------+------+---------+----------+---------+-------------+ 

分析

從explain的結(jié)果看,查詢用了b作為驅(qū)動(dòng)表。

上一篇文章我們介紹到,MySQL選擇jion順序是分別分析各種join順序的代價(jià)后,選擇最小代價(jià)的方法。

這個(gè)join只涉及到兩個(gè)表,自然也與optimizer_search_depth無關(guān)。于是我們的問題就是,我們預(yù)期的那個(gè)join順序的為什么沒有被選中?

MySQL Tips: MySQL提供straight_join語法,強(qiáng)制設(shè)定連接順序。

 

 
  1. explain SELECT count(*) FROM a straight_join b ON a.`S` = b.`S` WHERE a.`L` > '2014-03-30 00:55:00' AND a.`L` < '2014-03-30 01:00:00' ; 
  2.  
  3. +----+-------------+-------+-------+---------------+------+---------+------+---------+---------------------------------------------+ 
  4.  
  5. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 
  6.  
  7. +----+-------------+-------+-------+---------------+------+---------+------+---------+---------------------------------------------+ 
  8.  
  9. | 1 | SIMPLE | a | range | IX_L,IX_S | IX_L | 4 | NULL | 63 | Using where | 
  10.  
  11. | 1 | SIMPLE | b | index | IX_S | IX_S | 195 | NULL | 1038165 | Using where; Using index; Using join buffer | 
  12.  
  13. +----+-------------+-------+-------+---------------+------+---------+------+---------+---------------------------------------------+ 

MySQL Tips: explain結(jié)果中,join的查詢代價(jià)可以用依次連乘rows估算。

?join順序?qū)α耍?jiǎn)單的分析查詢代價(jià):普通join是1038165*1, straight_join是 63*1038165. 貌似MySQL沒有錯(cuò)。但一定哪里不對(duì)!

發(fā)現(xiàn)異常

回到我們最初的設(shè)想。我們預(yù)計(jì)表a作為驅(qū)動(dòng)表,是因?yàn)檎J(rèn)為表b能夠用上IX_S索引,而實(shí)際上staight_join的時(shí)候確實(shí)用上了,但這個(gè)結(jié)果與我們預(yù)期的又不同。

我們知道,索引的過濾性是決定了一個(gè)索引在查詢中是否會(huì)被選中的重要因素,那么是不是b.S的過濾性不好呢?

MySQL Tips: show index from tbname返回結(jié)果中Cardinality的值可以表明一個(gè)索引的過濾性。

show index的結(jié)果太多,也可以從information_schema表中取。

 

 
  1. mysql> select * from information_schema.STATISTICS where table_name='b' and index_name='IX_S'/G 
  2. *************************** 1. row *************************** 
  3. TABLE_CATALOG: def 
  4. TABLE_SCHEMA: test 
  5. TABLE_NAME: b 
  6. NON_UNIQUE: 1 
  7. INDEX_SCHEMA: test 
  8. INDEX_NAME: IX_S 
  9. SEQ_IN_INDEX: 1 
  10. COLUMN_NAME: S 
  11. COLLATION: A 
  12. CARDINALITY: 1038165 
  13. SUB_PART: NULL 
  14. PACKED: NULL 
  15. NULLABLE: YES 
  16. INDEX_TYPE: BTREE 
  17. COMMENT: 
  18. INDEX_COMMENT: 

可以這個(gè)索引的CARDINALITY: 1038165,已經(jīng)很大了。那這個(gè)表的估算行是多少呢。

 

 
  1. show table status like 'b'/G 
  2. *************************** 1. row *************************** 
  3. Name: b 
  4. Engine: InnoDB 
  5. Version: 10 
  6. Row_format: Compact 
  7. Rows: 1038165 
  8. Avg_row_length: 114 
  9. Data_length: 119160832 
  10. Max_data_length: 0 
  11. Index_length: 109953024 
  12. Data_free: 5242880 
  13. Auto_increment: NULL 
  14. Create_time: 2014-05-23 00:24:25 
  15. Update_time: NULL 
  16. Check_time: NULL 
  17. Collation: utf8_general_ci 
  18. Checksum: NULL 
  19. Create_options: 
  20. Comment: 
  21. 1 row in set (0.00 sec) 

從Rows: 1038165看出,IX_S這個(gè)索引的區(qū)分度被認(rèn)為非常好,已經(jīng)近似于唯一索引。

MySQL Tips: 在show table status結(jié)果中看到的Rows用于表示表的當(dāng)前行數(shù)。對(duì)于MyISAM表這是一個(gè)精確值,但對(duì)InnoDB這是個(gè)估算值。

雖然是估算值,但優(yōu)化器是以此為指導(dǎo)的,也就是說,上面的某個(gè)explain里面的數(shù)據(jù)完全不符合期望:staight_join結(jié)果中第二行的rows。

階段結(jié)論

我們發(fā)現(xiàn)整個(gè)錯(cuò)誤的邏輯是這樣的:以a為驅(qū)動(dòng)表的執(zhí)行計(jì)劃,由于索引b.S的rows估計(jì)為1038165導(dǎo)致優(yōu)化器認(rèn)為代價(jià)大于以b為驅(qū)動(dòng)表。而實(shí)際上這個(gè)索引的區(qū)分度為1.(當(dāng)然對(duì)explan結(jié)果比較熟悉的同學(xué)會(huì)發(fā)現(xiàn),第二行的type字段和Extra字段一起詭異了)

也就是說,straight_join得到的每一行去b中查詢的時(shí)候,都走了全表掃描。在MySQL里面出現(xiàn)這種情況的最常見的是類型轉(zhuǎn)換。比如一個(gè)字符串字段,雖然包含的是全數(shù)字,但查詢的時(shí)候傳入的不是字符串格式。

在這個(gè)case里面,兩個(gè)都是字符串。因此,就是字符集相關(guān)了。

回到兩個(gè)表結(jié)構(gòu),發(fā)現(xiàn)S字段的聲明差別在于 COLLATE utf8_bin -- 這個(gè)就是本case的根本原因了:a表得到的S值是utf8_bin,優(yōu)化器認(rèn)為類型不同,無法直接用上索引b.IX_S過濾。

至于為什么還會(huì)用上索引,這個(gè)是因?yàn)楦采w索引帶來“誤解”。

MySQL Tips:若查詢的所有結(jié)果能夠從某個(gè)索引完全得到,則會(huì)優(yōu)先用遍歷索引替代遍歷數(shù)據(jù)。

作為驗(yàn)證,

 

 
  1. mysql> explain SELECT * FROM a straight_JOIN b ON binary a.`S` = b.`S` WHERE a.`L` > '2014-03-30 00:55:00' AND a.`L` < '2014-03-30 01:00:00' ; 
  2.  
  3. +—-+————-+——-+——-+—————+——+———+——+———+————————————————+ 
  4.  
  5. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 
  6.  
  7. +—-+————-+——-+——-+—————+——+———+——+———+————————————————+ 
  8.  
  9. | 1 | SIMPLE | a | range | IX_L | IX_L | 4 | NULL | 63 | Using where | 
  10.  
  11. | 1 | SIMPLE | b | ALL | IX_S | NULL | NULL | NULL | 1038165 | Range checked for each record (index map: 0x4) | 
  12.  
  13. +—-+————-+——-+——-+—————+——+———+——+———+————————————————+ 

由于結(jié)果是select *, 無法使用覆蓋索引,因此第二行的key就顯示為NULL. (筆者淚:要是早出這個(gè)結(jié)果查起來可方便多了)

優(yōu)化

當(dāng)然最直接的想法就是修改兩個(gè)表的S字段的定義,改成相同即可。這個(gè)方法可以避免修改業(yè)務(wù)代碼,但DDL代價(jià)略大。這里提供兩種在SQL語句方面的優(yōu)化。

 

  1. select count(*) from b join (select s from a WHERE a.`L` > '2014-03-30 00:55:00' AND a.`L` < '2014-03-30 01:00:00') ta on b.S=ta.s; 

這個(gè)寫法比較直觀,需要注意最后b.S和ta.S的順序

 

 
  1. SELECT count(*) FROM a JOIN b ON binary a.`S` = b.`S` WHERE a.`L` > '2014-03-30 00:55:00' AND a.`L` < '2014-03-30 01:00:00' ; 

從前面的分析知道是由于b.S定義為utf8_bin.

MySQL Tips: MySQL中字符集命名規(guī)則中, XXX_bin與XXX的區(qū)別為大小寫是否敏感。

這里我們將A.s全部增加binary限定,先轉(zhuǎn)為小寫,就是將臨時(shí)結(jié)果集轉(zhuǎn)成utf8_bin,之后使用b.S匹配時(shí)就能夠直接利用索引。

其實(shí)兩個(gè)改寫方法的本質(zhì)相同,區(qū)別是寫法1是隱式轉(zhuǎn)換。理論上說寫法2速度更快些。

小結(jié)

做join的字段盡量設(shè)計(jì)為類型完全相同。

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 宽甸| 张北县| 东丰县| 建湖县| 湖州市| 江西省| 贡山| 平原县| 隆回县| 唐海县| 河津市| 渝中区| 彰武县| 南雄市| 汝州市| 洛宁县| 丹棱县| 大邑县| 台南市| 天柱县| 兴义市| 武胜县| 中方县| 孟州市| 奎屯市| 满城县| 英吉沙县| 丰原市| 神木县| 长岭县| 竹北市| 霸州市| 海南省| 沂源县| 邳州市| 女性| 卢湾区| 绩溪县| 苏尼特右旗| 罗江县| 霍城县|