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 之間查詢的列 <=使用的索引列+主鍵