SQL优化 · 经典案例 · 索引篇
Introduction
在這些年的工作之中,由于SQL問題導(dǎo)致的數(shù)據(jù)庫故障層出不窮,下面將過去六年工作中遇到的SQL問題總結(jié)歸類,還原問題原貌,給出分析問題思路和解決問題的方法,幫助用戶在使用數(shù)據(jù)庫的過程中能夠少走一些彎路。總共包括四部分:索引篇,SQL改寫篇,參數(shù)優(yōu)化篇,優(yōu)化器篇四部分,今天將介紹第一部分:索引篇。
索引問題是SQL問題中出現(xiàn)頻率最高的,常見的索引問題包括:無索引,隱式轉(zhuǎn)換。當(dāng)數(shù)據(jù)庫中出現(xiàn)訪問表的SQL無索引導(dǎo)致全表掃描,如果表的數(shù)據(jù)量很大,掃描大量的數(shù)據(jù),應(yīng)用請求變慢占用數(shù)據(jù)庫連接,連接堆積很快達(dá)到數(shù)據(jù)庫的最大連接數(shù)設(shè)置,新的應(yīng)用請求將會被拒絕導(dǎo)致故障發(fā)生。隱式轉(zhuǎn)換是指SQL查詢條件中的傳入值與對應(yīng)字段的數(shù)據(jù)定義不一致導(dǎo)致索引無法使用。常見隱士轉(zhuǎn)換如字段的表結(jié)構(gòu)定義為字符類型,但SQL傳入值為數(shù)字;或者是字段定義collation為區(qū)分大小寫,在多表關(guān)聯(lián)的場景下,其表的關(guān)聯(lián)字段大小寫敏感定義各不相同。隱式轉(zhuǎn)換會導(dǎo)致索引無法使用,進而出現(xiàn)上述慢SQL堆積數(shù)據(jù)庫連接數(shù)跑滿的情況。
無索引案例:
表結(jié)構(gòu)
CREATE TABLE `user` ( …… mo bigint NOT NULL DEFAULT '' , KEY ind_mo (mo) …… ) ENGINE=InnoDB;SELECT uid FROM `user` WHERE mo=13772556391 LIMIT 0,1執(zhí)行計劃
mysql> explain SELECT uid FROM `user` WHERE mo=13772556391 LIMIT 0,1;id: 1select_type: SIMPLEtable: usertype: ALL possible_keys: NULLkey: NULLrows: 707250Extra: Using where從上面的SQL看到執(zhí)行計劃中ALL,代表了這條SQL執(zhí)行計劃是全表掃描,每次執(zhí)行需要掃描707250行數(shù)據(jù),這是非常消耗性能的,該如何進行優(yōu)化?添加索引。?
驗證mo字段的過濾性
可以看到mo字段的過濾性是非常高的,進一步驗證可以通過select count(*) as all_count,count(distinct mo) as distinct_cnt from user,通對比 all_count和distinct_cnt這兩個值進行對比,如果all_cnt和distinct_cnt相差甚多,則在mo字段上添加索引是非常有效的。
添加索引
mysql> alter table user add index ind_mo(mo); mysql>SELECT uid FROM `user` WHERE mo=13772556391 LIMIT 0,1; Empty set (0.05 sec)執(zhí)行計劃
mysql> explain SELECT uid FROM `user` WHERE mo=13772556391 LIMIT 0,1\G; *************************** 1. row ***************************id: 1select_type: SIMPLEtable: usertype: indexpossible_keys: ind_mokey: ind_morows: 1Extra: Using where; Using index隱式轉(zhuǎn)換案例一
表結(jié)構(gòu)
CREATE TABLE `user` (……mo char(11) NOT NULL DEFAULT '' ,KEY ind_mo (mo)……) ENGINE=InnoDB;執(zhí)行計劃
mysql> explain extended select uid from`user` where mo=13772556391 limit 0,1; mysql> show warnings; Warning1:Cannot use index 'ind_mo' due to type or collation conversion on field 'mo' Note:select `user`.`uid` AS `uid` from `user` where (`user`.`mo` = 13772556391) limit 0,1如何解決
mysql> explain SELECT uid FROM `user` WHERE mo='13772556391' LIMIT 0,1\G; *************************** 1. row ***************************id: 1select_type: SIMPLEtable: usertype: refpossible_keys: ind_mokey: ind_morows: 1Extra: Using where; Using index上述案例中由于表結(jié)構(gòu)定義mo字段后字符串?dāng)?shù)據(jù)類型,而應(yīng)用傳入的則是數(shù)字,進而導(dǎo)致了隱式轉(zhuǎn)換,索引無法使用,所以有兩種方案:?
第一,將表結(jié)構(gòu)mo修改為數(shù)字?jǐn)?shù)據(jù)類型。?
第二,修改應(yīng)用將應(yīng)用中傳入的字符類型改為數(shù)據(jù)類型。
隱式轉(zhuǎn)換案例二
表結(jié)構(gòu)
CREATE TABLE `test_date` (`id` int(11) DEFAULT NULL,`gmt_create` varchar(100) DEFAULT NULL,KEY `ind_gmt_create` (`gmt_create`) ) ENGINE=InnoDB AUTO_INCREMENT=524272;5.5版本執(zhí)行計劃
mysql> explain select * from test_date where gmt_create BETWEEN DATE_ADD(NOW(), INTERVAL - 1 MINUTE) AND DATE_ADD(NOW(), INTERVAL 15 MINUTE) ; +----+-------------+-----------+-------+----------------+----------------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+-------+----------------+----------------+---------+------+------+-------------+ |1|SIMPLE| test_date |range| ind_gmt_create|ind_gmt_create|303| NULL | 1 | Using where |5.6版本執(zhí)行計劃
mysql> explain select * from test_date where gmt_create BETWEEN DATE_ADD(NOW(), INTERVAL - 1 MINUTE) AND DATE_ADD(NOW(), INTERVAL 15 MINUTE) ; +----+-------------+-----------+------+----------------+------+---------+------+---------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra| +----+-------------+-----------+------+----------------+------+---------+------+---------+-------------+ | 1 | SIMPLE| test_date | ALL | ind_gmt_create | NULL | NULL | NULL | 2849555 | Using where | +----+-------------+-----------+------+----------------+------+---------+------+---------+-------------+|Warning|Cannot use range access on index 'ind_gmt_create' due to type on field 'gmt_create'上述案例是用戶在5.5版本升級到5.6版本后出現(xiàn)的隱式轉(zhuǎn)換,導(dǎo)致數(shù)據(jù)庫cpu壓力100%,所以我們在定義時間字段的時候一定要采用時間類型的數(shù)據(jù)類型。
隱式轉(zhuǎn)換案例三
表結(jié)構(gòu)
CREATE TABLE `t1` (`c1` varchar(100) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,`c2` varchar(100) DEFAULT NULL,KEY `ind_c1` (`c1`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 CREATE TABLE `t2` (`c1` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,`c2` varchar(100) DEFAULT NULL,KEY `ind_c2` (`c2`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8執(zhí)行計劃
mysql> explain select t1.* from t2 left join t1 on t1.c1=t2.c1 where t2.c2='b'; +----+-------------+-------+------+---------------+--------+---------+-------+--------+-------------+ | id | select_type | table | type | possible_keys |key| key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+--------+---------+-------+--------+-------------+ | 1 | SIMPLE | t2 | ref | ind_c2 | ind_c2 | 303 | const | 258 | Using where | |1 |SIMPLE |t1 |ALL | NULL | NULL | NULL | NULL | 402250 | |修改COLLATE
mysql> alter table t1 modify column c1 varchar(100) COLLATE utf8_bin ; Query OK, 401920 rows affected (2.79 sec) Records: 401920 Duplicates: 0 Warnings: 0執(zhí)行計劃
mysql> explain select t1.* from t2 left join t1 on t1.c1=t2.c1 where t2.c2='b'; +----+-------------+-------+------+---------------+--------+---------+------------+-------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+--------+---------+------------+-------+-------------+ | 1 | SIMPLE| t2| ref | ind_c2| ind_c2 | 303 | const | 258 | Using where | | 1 |SIMPLE| t1|ref| ind_c1 | ind_c1 | 303 | test.t2.c1 | 33527 | | +----+-------------+-------+------+---------------+--------+---------+------------+-------+-------------+可以看到修改了字段的COLLATE后執(zhí)行計劃使用到了索引,所以一定要注意表字段的collate屬性的定義保持一致。
兩個索引的常見誤區(qū)
-
誤區(qū)一:對查詢條件的每個字段建立單列索引,例如查詢條件為:A=?and B=?and C=?。?
在表上創(chuàng)建了3個單列查詢條件的索引ind_A(A),ind_B(B),ind_C(C),應(yīng)該根據(jù)條件的過濾性,創(chuàng)建適當(dāng)?shù)膯瘟兴饕蛘呓M合索引。 -
誤區(qū)二:對查詢的所有字段建立組合索引,例如查詢條件為select A,B,C,D,E,F from T where G=?。?
在表上創(chuàng)建了ind_A_B_C_D_E_F_G(A,B,C,D,E,F,G)。
索引最佳實踐
- 在使用索引時,我們可以通過explain+extended查看SQL的執(zhí)行計劃,判斷是否使用了索引以及發(fā)生了隱式轉(zhuǎn)換。
- 由于常見的隱式轉(zhuǎn)換是由字段數(shù)據(jù)類型以及collation定義不當(dāng)導(dǎo)致,因此我們在設(shè)計開發(fā)階段,要避免數(shù)據(jù)庫字段定義,避免出現(xiàn)隱式轉(zhuǎn)換。
- 由于MySQL不支持函數(shù)索引,在開發(fā)時要避免在查詢條件加入函數(shù),例如date(gmt_create)。
- 所有上線的SQL都要經(jīng)過嚴(yán)格的審核,創(chuàng)建合適的索引。
總結(jié)
以上是生活随笔為你收集整理的SQL优化 · 经典案例 · 索引篇的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 表单文件按钮的优化
- 下一篇: ASP NET 数据库访问