SQL索引失效的11種情況詳析
數(shù)據(jù)庫(kù)調(diào)優(yōu)的大致方向:
- 索引失效,沒(méi)有充分利用到索引——建立索引
- 關(guān)聯(lián)查詢太多join——sql優(yōu)化
- 服務(wù)器調(diào)優(yōu)及各個(gè)參數(shù)設(shè)置——my.cnf
- 數(shù)據(jù)過(guò)多——分庫(kù)分表
sql查詢優(yōu)化技術(shù)有很多,大體分為物理查詢優(yōu)化和邏輯查詢優(yōu)化:
- 物理查詢優(yōu)化:通過(guò)索引和表連接方式等技術(shù)進(jìn)行優(yōu)化
- 邏輯查詢優(yōu)化:通過(guò)SQL等價(jià)變換提升查詢效率,就是換一種sql寫(xiě)法
數(shù)據(jù)準(zhǔn)備:
CREATE DATABASE atguigudb2; USE atguigudb2; ############# class 表 ################# CREATE TABLE `class` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `className` VARCHAR(30) DEFAULT NULL, `address` VARCHAR(40) DEFAULT NULL, `monitor` INT NULL , PRIMARY KEY (`id`) ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; ############# student 表 ################# CREATE TABLE `student` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `stuno` INT NOT NULL , `name` VARCHAR(20) DEFAULT NULL, `age` INT(3) DEFAULT NULL, `classId` INT(11) DEFAULT NULL, PRIMARY KEY (`id`) #CONSTRAINT `fk_class_id` FOREIGN KEY (`classId`) REFERENCES `t_class` (`id`) ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; ################################# SET GLOBAL log_bin_trust_function_creators=1; # 不加global只是當(dāng)前窗口有效。 #隨機(jī)產(chǎn)生字符串 DELIMITER // CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255) BEGIN DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ'; DECLARE return_str VARCHAR(255) DEFAULT ''; DECLARE i INT DEFAULT 0; WHILE i < n DO SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1)); SET i = i + 1; END WHILE; RETURN return_str; END // DELIMITER ; #假如要?jiǎng)h除 #drop function rand_string; #用于隨機(jī)產(chǎn)生多少到多少的編號(hào) DELIMITER // CREATE FUNCTION rand_num (from_num INT ,to_num INT) RETURNS INT(11) BEGIN DECLARE i INT DEFAULT 0; SET i = FLOOR(from_num +RAND()*(to_num - from_num+1)) ; RETURN i; END // DELIMITER ; #假如要?jiǎng)h除 #drop function rand_num; #創(chuàng)建往stu表中插入數(shù)據(jù)的存儲(chǔ)過(guò)程 DELIMITER // CREATE PROCEDURE insert_stu( START INT , max_num INT ) BEGIN DECLARE i INT DEFAULT 0; SET autocommit = 0; #設(shè)置手動(dòng)提交事務(wù) REPEAT #循環(huán) SET i = i + 1; #賦值 INSERT INTO student (stuno, NAME ,age ,classId ) VALUES ((START+i),rand_string(6),rand_num(1,50),rand_num(1,1000)); UNTIL i = max_num END REPEAT; COMMIT; #提交事務(wù) END // DELIMITER ; #執(zhí)行存儲(chǔ)過(guò)程,往class表添加隨機(jī)數(shù)據(jù) DELIMITER // CREATE PROCEDURE `insert_class`( max_num INT ) BEGIN DECLARE i INT DEFAULT 0; SET autocommit = 0; REPEAT SET i = i + 1; INSERT INTO class ( classname,address,monitor ) VALUES (rand_string(8),rand_string(10),rand_num(1,100000)); UNTIL i = max_num END REPEAT; COMMIT; END // DELIMITER ; #執(zhí)行存儲(chǔ)過(guò)程,往class表添加1萬(wàn)條數(shù)據(jù) CALL insert_class(10000); #執(zhí)行存儲(chǔ)過(guò)程,往stu表添加50萬(wàn)條數(shù)據(jù) CALL insert_stu(100000,500000); SELECT COUNT(*) FROM class; SELECT COUNT(*) FROM student; ############################### 刪除索引的存儲(chǔ)過(guò)程 ######################## DELIMITER // CREATE PROCEDURE `proc_drop_index`(dbname VARCHAR(200),tablename VARCHAR(200)) BEGIN DECLARE done INT DEFAULT 0; DECLARE ct INT DEFAULT 0; DECLARE _index VARCHAR(200) DEFAULT ''; DECLARE _cur CURSOR FOR SELECT index_name FROM information_schema.STATISTICS WHERE table_schema=dbname AND table_name=tablename AND seq_in_index=1 AND index_name <>'PRIMARY' ; #每個(gè)游標(biāo)必須使用不同的declare continue handler for not found set done=1來(lái)控制游標(biāo)的結(jié)束 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=2 ; #若沒(méi)有數(shù)據(jù)返回,程序繼續(xù),并將變量done設(shè)為2 OPEN _cur; FETCH _cur INTO _index; WHILE _index<>'' DO SET @str = CONCAT("drop index " , _index , " on " , tablename ); PREPARE sql_str FROM @str ; EXECUTE sql_str; DEALLOCATE PREPARE sql_str; SET _index=''; FETCH _cur INTO _index; END WHILE; CLOSE _cur; END // DELIMITER ; # 執(zhí)行存儲(chǔ)過(guò)程 CALL proc_drop_index("dbname","tablename");
索引失效案例
【1】. 全值匹配
# 【1】. 全值匹配 # student表,主鍵id,此時(shí)無(wú)索引,耗時(shí)大 EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 30; EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 30 AND classId = 4; EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 30 AND classId = 4 AND NAME = 'abcd'; # 注:SQL_NO_CACHE 不使用查詢緩存 # 建立索引 CREATE INDEX idx_age ON student(age); CREATE INDEX idx_age_classid ON student(age,classId); CREATE INDEX idx_age_classid_name ON student(age,classId,NAME); # 此時(shí)第三條查詢語(yǔ)句默認(rèn)使用最后一條索引,而不是前兩個(gè)
【2】. 最佳左前綴法則
# 【2】. 最佳左前綴法則 EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.age = 30 AND student.name = 'abcd'; # 查age&name,用age的索引 EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.classid = 1 AND student.name = 'abcd'; # 查classid&name,classid在前,有索引的話先找classid相同的,再找name, #但現(xiàn)在沒(méi)有這樣的索引,idx_age_classid_name的字段順序是先找age,所以不符合,所以此時(shí)不能用索引 EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE classid = 4 AND student.age = 30 AND student.name = 'abcd'; #idx_age_classid_name 聯(lián)合索引中所有字段均出現(xiàn),可以使用該索引 EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.age = 30 AND student.name = 'abcd'; # 現(xiàn)在,刪除idx_age和idx_age_classid,發(fā)現(xiàn)用到idx_age_classid_name,而key_len=5,即只用到age字段,int(4)+null(1) #因?yàn)樗饕阛ge后沒(méi)有classid了,不能再查找到name
【3】. 主鍵插入順序
在定義表時(shí),讓主鍵auto_increment,否則,插入一條數(shù)據(jù)時(shí)可能會(huì)移動(dòng)大量數(shù)據(jù)。
如,往 1 5 8 10 15 … 100 中插9,會(huì)放在8 10 中間,因?yàn)樗饕J(rèn)升序排列。那么10往后的數(shù)據(jù)都要挪動(dòng),頁(yè)不夠時(shí)又要放到下一頁(yè),每插一條數(shù)據(jù)都這樣挪一次,開(kāi)銷很大
我們自定義的主鍵列id 擁有AUTO_INCREMENT 屬性,在插入記錄時(shí)存儲(chǔ)引擎會(huì)自動(dòng)為我們填入自增的主鍵值。這樣的主鍵占用空間小,順序?qū)懭?,減少頁(yè)分裂。
【4】. 計(jì)算、函數(shù)、類型轉(zhuǎn)換(自動(dòng)或手動(dòng))導(dǎo)致索引失效
# 【4】. 計(jì)算、函數(shù)、類型轉(zhuǎn)換(自動(dòng)或手動(dòng))導(dǎo)致索引失效 ##### 例1: EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name LIKE 'abc%'; #更好,能夠使用上索引 # type=range 使用了索引中的排序 EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE LEFT(student.name,3) = 'abc'; # left(text,num_chars):截取左側(cè)n個(gè)字符 # type = all 全表的訪問(wèn) # 該語(yǔ)句的執(zhí)行過(guò)程:針對(duì)每一條數(shù)據(jù),一個(gè)一個(gè)取出,先作用一遍函數(shù),再拿函數(shù)結(jié)果與abc對(duì)比,用不上b+樹(shù) CREATE INDEX idx_name ON student(NAME); ##### 例2: CREATE INDEX idx_sno ON student(stuno); EXPLAIN SELECT SQL_NO_CACHE id,stuno,NAME FROM student WHERE stuno+1 = 900001; # type = all 需要做運(yùn)算,無(wú)法直接用索引找值 EXPLAIN SELECT SQL_NO_CACHE id,stuno,NAME FROM student WHERE stuno = 900000; # type = ref
【5】. 類型轉(zhuǎn)換導(dǎo)致索引失效
# 【5】. 類型轉(zhuǎn)換導(dǎo)致索引失效 # 未使用到索引 EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE NAME=123; # 這里使用了隱式轉(zhuǎn)換 # 使用到索引 EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE NAME='123'; # name本身就是字符串類型
【6】. 范圍條件右邊的列索引失效
# 【6】. 范圍條件右邊的列索引失效 ( > < >= <= between 等) SHOW INDEX FROM student; CALL proc_drop_index('atguigudb2','student'); CREATE INDEX idx_age_classid_name ON student(age,classId,NAME); EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.age = 30 AND student.classId > 20 AND student.name = 'abc'; # 這三個(gè)and先寫(xiě)誰(shuí)無(wú)所謂,優(yōu)化器會(huì)調(diào)優(yōu) # key_len = 10, age=5,classId=5,name用不上。classId 是范圍,索引右側(cè)的name用不上 # 改寫(xiě)索引: CREATE INDEX idx_age_name_cid ON student(age,NAME,classId); #把需要排序的classid放到最后 # 此時(shí)在執(zhí)行上面的語(yǔ)句,就使用了這個(gè)索引,key_len=73
創(chuàng)建的聯(lián)合索引中,必須把涉及到范圍的字段寫(xiě)在最后。
【7】. 不等于(!= 或者<>)索引失效
# 【7】. 不等于(!= 或者<>)索引失效 CREATE INDEX idx_name ON student(NAME); EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name <> 'abc'; # 索引失效 索引查的是等于
【8】. is null可以使用索引,is not null無(wú)法使用索引
# 【8】. is null可以使用索引,is not null無(wú)法使用索引 EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age IS NULL; # type=ref 相當(dāng)于等于某個(gè)值 EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age IS NOT NULL; # 索引失效 相當(dāng)于不等于
【9】. like以通配符%開(kāi)頭索引失效
# 【9】. like以通配符%開(kāi)頭索引失效 EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE NAME LIKE 'ab%'; # 可用索引 EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE NAME LIKE '%ab'; # type = all 索引失效
頁(yè)面搜索嚴(yán)禁左模糊或者全模糊,如果需要請(qǐng)走搜索引擎來(lái)解決。
【10】. OR 前后存在非索引的列,索引失效
# 【10】. OR 前后存在非索引的列,索引失效 CALL proc_drop_index('atguigudb2','student'); SHOW INDEX FROM student; # 創(chuàng)建一個(gè)age的索引 CREATE INDEX idx_age ON student(age); EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 10 OR classid = 100; # 未使用索引,索引+全表掃描->全表掃描 # 再加一個(gè)字段的單獨(dú)索引 CREATE INDEX idx_cid ON student(classid); # 再執(zhí)行上條語(yǔ)句,此時(shí) type = index_merge ,key = idx_age,idx_cid。
【11】. 數(shù)據(jù)庫(kù)和表的字符集統(tǒng)一使用utf8mb4
統(tǒng)一使用utf8mb4( 5.5.3版本以上支持)兼容性更好,統(tǒng)一字符集可以避免由于字符集轉(zhuǎn)換產(chǎn)生的亂碼。不同的字符集進(jìn)行比較前需要進(jìn)行轉(zhuǎn)換會(huì)造成索引失效。
總結(jié)
到此這篇關(guān)于SQL索引失效的11種情況詳析的文章就介紹到這了,更多相關(guān)SQL索引失效內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于k8s環(huán)境部署mysql主從的問(wèn)題
這篇文章主要介紹了k8s環(huán)境部署mysql主從的問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03SPSS連接mysql數(shù)據(jù)庫(kù)的超詳細(xì)操作教程
小編最近在學(xué)習(xí)SPSS,在為數(shù)據(jù)庫(kù)建立連接時(shí)真的踩了很多坑,這篇文章主要給大家介紹了關(guān)于SPSS連接mysql數(shù)據(jù)庫(kù)的超詳細(xì)操作教程,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02mysql存儲(chǔ)過(guò)程基礎(chǔ)之遍歷多表記錄后插入第三方表中詳解
這篇文章主要給大家介紹了關(guān)于mysql存儲(chǔ)過(guò)程教程之遍歷多表記錄后插入第三方表中的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧2018-07-07MySQL事務(wù)(transaction)看這篇就足夠了
M事務(wù)的實(shí)現(xiàn)是基于數(shù)據(jù)庫(kù)的存儲(chǔ)引擎,不同的存儲(chǔ)引擎對(duì)事務(wù)的支持程度不一樣,下面這篇文章主要給大家介紹了關(guān)于MySQL事務(wù)(transaction)的相關(guān)資料,需要的朋友可以參考下2022-11-11MySQL主從復(fù)制之半同步semi-sync?replication
這篇文章主要介紹了MySQL主從復(fù)制之半同步semi-sync?replication,半同步相對(duì)于異步復(fù)制而言,提高了數(shù)據(jù)的安全性,同時(shí)也造成了一定程度的延遲,這個(gè)延遲最少是一個(gè)TCP往返的時(shí)間。所以,半同步復(fù)制最好在低延時(shí)的網(wǎng)絡(luò)中使用,下文詳細(xì)內(nèi)容,需要的小伙伴可以參考一下2022-02-02mysql實(shí)現(xiàn)將字符串字段轉(zhuǎn)為數(shù)字排序或比大小
這篇文章主要介紹了mysql實(shí)現(xiàn)將字符串字段轉(zhuǎn)為數(shù)字排序或比大小,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06MySQL分區(qū)表實(shí)現(xiàn)按月份歸類
mysql 單表數(shù)據(jù)量達(dá)到千萬(wàn)、億級(jí),可以通過(guò)分表與表分區(qū)提升服務(wù)性能。本文主要介紹了MySQL分區(qū)表實(shí)現(xiàn)按月份歸類,感興趣的可以了解一下2021-10-10Navicat for Mysql 字段注釋中文亂碼問(wèn)題及解決
這篇文章主要介紹了Navicat for Mysql 字段注釋中文亂碼問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09