1. 程式人生 > >Mysql學習-04 索引優化分析--查詢優化

Mysql學習-04 索引優化分析--查詢優化

索引的規則

1.索引失效情況

<1>全值匹配我最愛 

    建立索引提高效率 CREATE INDEX 索引名字 ON 表名(表字段,表字段,表字段,......)

   建立索引前:

   

   索引後:

     

<2>最佳左字首法則
     如果索引了多列,要遵守最左字首法則。指的是查詢從索引的最左前列開始並且不跳過索引中的列。 

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

  

 雖然只是使用了一部分但是效率還是提高了。

 

 完全沒有使用上索引

 結論:過濾條件要使用索引必須按照索引建立時的順序,依次滿足,一旦跳過某個欄位,索引後面的欄位都無法被使用。

<3>不在索引列上做任何操作(計算、函式、(自動or手動)型別轉換),會導致索引失效而轉向全表掃描

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

第一種:

  

第二種:

  

<4>儲存引擎不能使用索引中範圍條件右邊的列,可以將範圍索引欄位放置到索引中的最右位置

 如果系統經常出現的sql如下:
 SELECT  SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.deptId>20 AND emp.name = 'abc' ; 
 那麼索引 idx_age_deptid_name這個索引還能正常使用麼?

如果這種sql 出現較多。應該建立: create index idx_age_name_deptid on emp(age,name,deptid)
效果:

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

<6>is not null 也無法使用索引,但是is null是可以使用索引的

<7>like以萬用字元開頭('%abc...')mysql索引失效會變成全表掃描的操作

<8>字串不加單引號索引失效

總結:

2.建議

  •   對於單鍵索引,儘量選擇針對當前query過濾性更好的索引
  •   在選擇組合索引的時候,當前Query中過濾性最好的欄位在索引欄位順序中,位置越靠前越好。
  •   在選擇組合索引的時候,儘量選擇可以能夠包含當前query中的where字句中更多欄位的索引
  •   書寫sql語句時,儘量避免造成索引失效的情況
  •   儘可能通過分析統計資訊和調整query的寫法來達到選擇合適索引的目的

單表查詢優化:

        當範圍條件和group by 或者 order by  的欄位出現二選一時 ,優先觀察條件欄位的過濾數量,如果過濾的資料足夠多,而需要排序的資料並不多時,優先把索引放在範圍欄位上。反之,亦然。
 

關聯查詢優化:如 left join /right join/union等

建議

  • 保證被驅動表的join欄位已經被索引,該索引因該給屬於驅動表中的欄位
  • left join 時,選擇小表作為驅動表,大表作為被驅動表。
  • inner join 時,mysql會自己幫你把小結果集的表選為驅動表。
  • 子查詢儘量不要放在被驅動表,有可能使用不到索引。

子查詢優化:
   儘量不要使用not in  或者 not exists 用left outer join  on  xxx is null 替代

  如:

SELECT SQL_NO_CACHE * FROM emp a WHERE  id  NOT  IN(SELECT ceo FROM dept b2
 WHERE ceo IS NOT NULL) group by age having count(*)<10000

修改:

  EXPLAIN SELECT SQL_NO_CACHE a.* FROM  emp a LEFT OUTER JOIN dept b
  ON a.id =b.ceo WHERE    b.ceo IS   NULL
  group by age having count(*)<10000

order by關鍵字優化 

  • 儘可能在索引列上完成排序操作,遵照索引建的最佳左字首

      

CREATE TABLE `emp` (
 `id` INT(11) NOT NULL AUTO_INCREMENT,
 `name` VARCHAR(20) DEFAULT NULL,
  `age` INT(3) DEFAULT NULL,
 `deptid` INT(11) DEFAULT NULL,
  empno int  not null,
 PRIMARY KEY (`id`),
 KEY `fk_dept_id` (`deptId`)

) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
create index idx_age_deptid_name on emp (age,deptid,name) -- 建立索引
drop index idx_age_deptid_name on emp -- 刪除索引
以下  是否能使用到索引,能否去掉using filesort 
1、explain  select * from emp order by age,deptid; -- Using filesort
不能 沒有過濾條件 
2、 explain  select * from emp order by age,deptid limit 10; -- Using filesort
 #若無過濾排序折
3、  explain  select * from emp where age=45 order by deptid; -- Using where
4、explain  select * from emp where age=45 order by   deptid,name; -- Using where
5、explain  select * from emp where age=45 order by  deptid,empno;-- Using where; Using filesort
6、explain  select * from emp where age=45 order by  name,deptid;-- Using where; Using filesort
7、 explain select * from emp where deptid=45 order by age; -- Using where; Using filesort 
#順序不對排序折
8、explain  select * from emp where age=45 order by age,deptid; -- Using where 
9、explain  select * from emp where age=45 order by  deptid,age; -- Using where
#值確定無須排序 
 10、 EXPLAIN  SELECT * FROM emp WHERE age=50 AND deptid >8000 ORDER BY NAME; -- Using index condition; Using where; Using filesort
11、  EXPLAIN  SELECT * FROM emp WHERE age=50 AND deptid >8000 ORDER BY deptid, NAME; -- Using index condition; Using where 
 #誰殘還得誰來續
12、  explain select * from emp where age=45 order by  deptid desc, name desc ; -- Using where
13、 explain select * from emp where age=45 order by  deptid asc, name desc ;-- Using where; Using filesort
#南轅北轍排序折(排序順序不一致)
  • ORDER BY子句,儘量使用Index方式排序,避免使用FileSort方式排序
  • 如果不在索引列上,filesort有兩種演算法:mysql就要啟動雙路排序和單路排序

     1.雙路排序

         MySQL 4.1之前是使用雙路排序,字面意思就是兩次掃描磁碟,最終得到資料, 讀取行指標和orderby列,對他們進行排序,然後掃描已經排序好的列表,按照列表中的值重新從列表中讀取對應的資料輸出。從磁碟取排序欄位,在buffer進行排序,再從磁碟取其他欄位。取一批資料,要對磁碟進行了兩次掃描,眾所周知,I\O是很耗時的,所以在mysql4.1之後,出現了第二種改進的演算法,就是單路排序。

    3.單路排序
         從磁碟讀取查詢需要的所有列,按照order by列在buffer對它們進行排序,然後掃描排序後的列表進行輸出, 它的效率更快一些,避免了第二次讀取資料。並且把隨機IO變成了順序IO,但是它會使用更多的空間, 因為它把每一行都儲存在記憶體中了。

    結論及引申出的問題
        由於單路是後出的,總體而言好過雙路, 但是用單路有問題 :在sort_buffer中,方法B比方法A要多佔用很多空間,因為方法B是把所有欄位都取出, 所以有可能取出的資料的總大小超出了sort_buffer的容量,導致每次只能取sort_buffer容量大小的資料,進行排序(建立tmp檔案,多路合併),排完再取取sort_buffer容量大小,再排……從而多次I/O。本來想省一次I/O操作,反而導致了大量的I/O操作,反而得不償失。

  4.優化策略
             增大sort_buffer_size引數的設定
             增大max_length_for_sort_data引數的設定
             減少select 後面的查詢的欄位。

     
  提高Order By的速度
      1. Order by時select * 是一個大忌只Query需要的欄位, 這點非常重要。在這裡的影響是:
         1.1 當Query的欄位大小總和小於max_length_for_sort_data 而且排序欄位不是 TEXT|BLOB 型別時,會用改進後的演算法——單路排序, 否則用老演算法——多路排序。
         1.2 兩種演算法的資料都有可能超出sort_buffer的容量,超出之後,會建立tmp檔案進行合併排序,導致多次I/O,但是用單路排序演算法的風險會更大一些,所以要提高sort_buffer_size。
     2. 嘗試提高 sort_buffer_size
        不管用哪種演算法,提高這個引數都會提高效率,當然,要根據系統的能力去提高,因為這個引數是針對每個程序的  1M-8M之間調整
     3. 嘗試提高 max_length_for_sort_data
         提高這個引數, 會增加用改進演算法的概率。但是如果設的太高,資料總容量超出sort_buffer_size的概率就增大,明顯症狀是高的磁碟I/O活動和低的處理器使用率.  1024-8096之間調整 

GROUP BY關鍵字優化

  •   group by實質是先排序後進行分組,遵照索引建的最佳左字首
  •   當無法使用索引列,增大max_length_for_sort_data引數的設定+增大sort_buffer_size引數的設定
  •   where高於having,能寫在where限定的條件就不要去having限定了。 

 分頁查詢的優化---limit

 EXPLAIN    SELECT  SQL_NO_CACHE * FROM emp  ORDER  BY  deptid  LIMIT 10000,40

給deptno這個欄位加上索引後:

 

沒什麼用。因為優化器認為limit的過濾效果並不好,所以放棄了使用索引過濾。 

優化:  先利用覆蓋索引把要取的資料行的主鍵取到,然後再用這個主鍵列與資料表做關聯:
EXPLAIN  SELECT  SQL_NO_CACHE * FROM emp INNER JOIN (SELECT id FROM emp e ORDER BY deptno LIMIT 10000,40) a ON a.id=emp.id
 

覆蓋索引 --> 就是,select 到 from 之間查詢的列 <=使用的索引列+主鍵