MySQL優化---單表索引失效原因及優化策略

一、數據準備

往表裏插50W數據以測試我們的SQL

建表語句

 CREATE TABLE `dept` (
 `id` INT(11) NOT NULL AUTO_INCREMENT,
 `deptName` VARCHAR(30) DEFAULT NULL,
 `address` VARCHAR(40) DEFAULT NULL,
 ceo INT NULL ,
 PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
 
 
CREATE TABLE `emp` (
 `id` INT(11) NOT NULL AUTO_INCREMENT,
 `empno` INT NOT NULL ,
 `name` VARCHAR(20) DEFAULT NULL,
 `age` INT(3) DEFAULT NULL,
 `deptId` INT(11) DEFAULT NULL,
 PRIMARY KEY (`id`)
 #CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

創建函數,保證每條數據都不同。

 
 
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 $$
 
 
#假如要刪除
#drop function rand_string;
 
#用於隨機產生多少到多少的編號
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$$ 
 
#假如要刪除
#drop function rand_num;

創建插入數據的存儲過程

 
 
DELIMITER $$
CREATE PROCEDURE  insert_emp(  START INT ,  max_num INT )
BEGIN  
DECLARE i INT DEFAULT 0;   
#set autocommit =0 把autocommit設置成0  
 SET autocommit = 0;    
 REPEAT  
 SET i = i + 1;  
 INSERT INTO emp (empno, NAME ,age ,deptid ) VALUES ((START+i) ,rand_string(6)   , rand_num(30,50),rand_num(1,10000));  
 UNTIL i = max_num  
 END REPEAT;  
 COMMIT;  
 END$$ 
 
#刪除
# DELIMITER ;
# drop PROCEDURE insert_emp;
 
 
#執行存儲過程,往dept表添加隨機數據
DELIMITER $$
CREATE PROCEDURE `insert_dept`(  max_num INT )
BEGIN  
DECLARE i INT DEFAULT 0;   
 SET autocommit = 0;    
 REPEAT  
 SET i = i + 1;  
 INSERT INTO dept ( deptname,address,ceo ) VALUES (rand_string(8),rand_string(10),rand_num(1,500000));  
 UNTIL i = max_num  
 END REPEAT;  
 COMMIT;  
 END$$
 
#刪除
# DELIMITER ;
# drop PROCEDURE insert_dept;

執行存儲過程,插入數據

 
 #執行存儲過程,往dept表添加1萬條數據
DELIMITER ;
CALL insert_dept(10000); 

#執行存儲過程,往emp表添加50萬條數據
DELIMITER ;
CALL insert_emp(100000,500000); 

寫個批量刪除表上的某個索引的存儲過程

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'  ;
       DECLARE  CONTINUE HANDLER FOR NOT FOUND set done=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$$

如果我們想刪除某個表的索引,只需要調用此存儲過程即可

CALL proc_drop_index("dbname","tablename");

二、導致索引失效的幾個情況

1.全值匹配我最愛:系統經常出現的sql語句爲

EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=4 AND emp.name = 'abcd'  

如何建立索引比較好?所用到的查詢條件都應建立爲一個複合索引

CREATE INDEX idx_age_deptid_name ON emp(age,deptid,NAME)

建立索引前

 
索引後

2.最佳左前綴法則:如果建立了一個複合索引,在查詢時最好遵循最左前綴法則。即查詢時從複合索引的第一個索引開始並且查詢條件中不跳過索引的列。

假如經常出現的sql爲

 EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30   AND emp.name = 'abcd'   
或者
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.deptid=1   AND emp.name = 'abcd'   

那原來的索引idx_age_deptid_name還會生效嗎?

只用到了一部分,可以通過key_len看出,只用到了deptid這個字段的索引,後面的索引沒有用到。

這個查詢則完全沒有用到索引。

這與索引建立的原理有關,索引是一個排好序的快速查找的數據結構,在建立一個複合索引的過程中,索引會從第一個字段開始排序,再從這基礎上進行第二,第三個索引字段的排序,類似蓋樓的順序,先從一樓開始蓋,再從一樓的基礎上蓋二樓、三樓。所以我們查詢語句中的過濾條件要想使用到索引,就必須按照索引建立的順序,依次滿足,一旦跳過某個字段,則該字段對應的索引之後的索引都無法使用,這也就是爲什麼第一條sql只用到了deptid的索引,而第二個sql根本沒有用到索引的原因。

3.不要在索引列上做任何操作(計算、函數、類型轉換),這也會導致索引失效而轉向全表掃描。

這兩條sql哪種寫法更好
EXPLAIN  SELECT SQL_NO_CACHE * FROM emp WHERE   emp.name  LIKE 'abc%' 
 
EXPLAIN   SELECT SQL_NO_CACHE * FROM emp WHERE   LEFT(emp.name,3)  = 'abc'

不論是哪一種,既然用到了name字段,我們就要依照“全值匹配我最愛”,在name列建立索引。

create index idx_name on emp(name)

第一個SQL 用到了索引 且type理想,沒有什麼問題。

第二個SQL,索引失效了,type變爲了全表掃描。

所以在索引列上不要做任何操作,否則會索引失效

4.複合索引中範圍查詢右邊的列會失效

執行這個SQL,發現用到了索引,但根據key_len可得知只用到了age和deptid這兩個索引,name的索引失效了

當在索引列上有範圍查詢時,範圍查詢對應的索引字段右邊的索引會失效,這裏指的是建立索引的順序,不是where後查詢的順序,因爲MySQL的optimizer查詢優化器會將我們的where查詢自動按索引順序進行優化。

如果這樣的SQL較多,我們可以在建立索引的時候將範圍查詢的字段建立到最後。

create index idx_age_name_deptid on emp(age,name,deptid)

這樣再進行查詢,發現索引正常。

5.在使用不等於(!= 或者 <>)的時候索引會失效導致全表掃描

 CREATE INDEX idx_name ON emp(NAME)
  
  EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE   emp.name <>  'abc' 
 

6.使用is not null 會導致索引失效

is null 會正常使用索引,但is not null會導致索引失效

7.like模糊查詢的條件以%開頭會導致索引失效

8.字符串不加單引號導致索引失效

三、總結案例

四、總結優化建議

1.對於單鍵索引,儘量選擇針對當前query過濾性更好的索引

不要用性別這種過濾性特別差的字段建立索引,儘量建立在比較唯一定位一條記錄的字段,如手機號,身份證號等。

2.在選擇組合索引的時候,當前Query中過濾性最好的字段在索引字段順序中,位置越靠前越好。

根據複合索引的層級排序結構,在第一層就比較精準的定位一個或幾個數據有利於查詢優化。

3.在選擇組合索引的時候,儘量選擇可以能夠包含當前query中的where字句中更多字段的索引(全值匹配我最愛)

4.在選擇組合索引的時候,如果某個字段可能出現範圍查詢時,儘量把這個字段放在索引次序的最後面

5.儘量避免造成索引失效的情況

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章