大多情況下我們都知道加索引能提高查詢效率,但是應該如何加索引呢?索引的順序如何呢?
大家看一下下面的sql語句(在沒有看下面的優化的方法之前)應該如何優化加索引以及優化sql語句:
1、select count(*) from task where status=2 and operator_id=20839 and operate_time>1371169729 and operate_time<1371174603 and type=2;
2、1、select count(*) from task where from_unixtime(create_time) = ’2014-05-29’;
3、update test set name='zhangsan' where sex=1 and age>20;(注意這種情況加索引與不加索引的區別是什么)
4、select distinct l.emp_id from log l inner join (select l2.id as emp_id,l2.id as cert_id from log2 l2 left join log3 l3 on l2.id = l3.emp_id where l2.is_deleted=0 ) l4 on ( l.ref_table='Employee' and l.ref_oid= l4.emp_id
) or (l.ref_table='EmpCertificate' and l.ref_oid= l2.cert_id) where l.last_upd_date >='2013-11-07 15:03:00' and l.last_upd_date<='2013-11-08 16:00:00'
下面說一下MySql索引原理
索引目的
索引的目的在於提高查詢效率,可以類比字典,如果要查“mysql”這個單詞,我們肯定需要定位到m字母,然后從下往下找到y字母,再找到剩下的sql。如果沒有索引,那么你可能需要把所有單詞看一遍才能找到你想要的,如果我想找到m開頭的單詞呢?或者ze開頭的單詞呢?是不是覺得如果沒有索引,這個事情根本無法完成?
索引原理
除了詞典,生活中隨處可見索引的例子,如火車站的車次表、圖書的目錄等。它們的原理都是一樣的,通過不斷的縮小想要獲得數據的范圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是我們總是通過同一種查找方式來鎖定數據。
數據庫也是一樣,但顯然要復雜許多,因為不僅面臨着等值查詢,還有范圍查詢(>、<、between、in)、模糊查詢(like)、並集查詢(or)等等。數據庫應該選擇怎么樣的方式來應對所有的問題呢?我們回想字典的例子,能不能把數據分成段,然后分段查詢呢?最簡單的如果1000條數據,1到100分成第一段,101到200分成第二段,201到300分成第三段……這樣查第250條數據,只要找第三段就可以了,一下子去除了90%的無效數據。但如果是1千萬的記錄呢,分成幾段比較好?稍有算法基礎的同學會想到搜索樹,其平均復雜度是lgN,具有不錯的查詢性能。但這里我們忽略了一個關鍵的問題,復雜度模型是基於每次相同的操作成本來考慮的,數據庫實現比較復雜,數據保存在磁盤上,而為了提高性能,每次又可以把部分數據讀入內存來計算,因為我們知道訪問磁盤的成本大概是訪問內存的十萬倍左右,所以簡單的搜索樹難以滿足復雜的應用場景。
索引的數據結構
前面講了生活中索引的例子,索引的基本原理,數據庫的復雜性,又講了操作系統的相關知識,目的就是讓大家了解,任何一種數據結構都不是憑空產生的,一定會有它的背景和使用場景,我們現在總結一下,我們需要這種數據結構能夠做些什么,其實很簡單,那就是:每次查找數據時把磁盤IO次數控制在一個很小的數量級,最好是常數數量級。那么我們就想到如果一個高度可控的多路搜索樹是否能滿足需求呢?就這樣,b+樹應運而生。
建索引的幾大原則
1.最左前綴匹配原則,非常重要的原則,mysql會一直向右匹配直到遇到范圍查詢(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)順序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引則都可以用到,a,b,d的順序可以任意調整。
2.=和in可以亂序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意順序,mysql的查詢優化器會幫你優化成索引可以識別的形式
3.盡量選擇區分度高的列作為索引,區分度的公式是count(distinct col)/count(*),表示字段不重復的比例,比例越大我們掃描的記錄數越少,唯一鍵的區分度是1,而一些狀態、性別字段可能在大數據面前區分度就是0,那可能有人會問,這個比例有什么經驗值嗎?使用場景不同,這個值也很難確定,一般需要join的字段我們都要求是0.1以上,即平均1條掃描10條記錄
4.索引列不能參與計算,保持列“干凈”,比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很簡單,b+樹中存的都是數據表中的字段值,但進行檢索時,需要把所有元素都應用函數才能比較,顯然成本太大。所以語句應該寫成create_time = unix_timestamp(’2014-05-29’);
5.盡量的擴展索引,不要新建索引。比如表中已經有a的索引,現在要加(a,b)的索引,那么只需要修改原來的索引即可
回到上面的給出的mysql
1、根據最左匹配原則,第一條sql語句的索引應該是status、operator_id、type、operate_time的聯合索引;其中status、operator_id、type的順序可以顛倒,所以我才會說,把這個表的所有相關查詢都找到,會綜合分析;
2、根據索引列不能參與計算,保持列“干凈”,第二條sql語句應該寫成create_time = unix_timestamp(’2014-05-29’);
3、update 語句不加索引是鎖表,加索引是鎖行 where后面的條件根據第一條一樣優化即可。
4、第4條sql語句就復雜了在沒有做如何優化之前結果是:53
rows
in
set
(
1.87
sec)
通過explain
簡述一下執行計划,首先mysql根據idx_last_upd_date索引掃描log表獲得379條記錄;然后查表掃描了63727條記錄,分為兩部分,derived表示構造表,也就是不存在的表,可以簡單理解成是一個語句形成的結果集,后面的數字表示語句的ID。derived2表示的是ID = 2的查詢構造了虛擬表,並且返回了63727條記錄。我們再來看看ID = 2的語句究竟做了寫什么返回了這么大量的數據,首先全表掃描log2表13317條記錄,然后根據索引emp_certificate_empid關聯log3表,rows = 1表示,每個關聯都只鎖定了一條記錄,效率比較高。獲得后,再和log的379條記錄根據規則關聯。從執行過程上可以看出返回了太多的數據,返回的數據絕大部分log都用不到,因為log只鎖定了379條記錄。
如何優化呢?可以看到我們在運行完后還是要和log做join,那么我們能不能之前和log做join呢?仔細分析語句不難發現,其基本思想是如果log的ref_table是EmpCertificate就關聯log3表,如果ref_table是Employee就關聯log2表,我們完全可以拆成兩部分,並用union連接起來,注意這里用union,而不用union all是因為原語句有“distinct”來得到唯一的記錄,而union恰好具備了這種功能。如果原語句中沒有distinct不需要去重,我們就可以直接使用union all了,因為使用union需要去重的動作,會影響SQL性能。
優化過的語句如下
select
l2.id
from
log l
inner join
log2 l2
on l.ref_table = 'Employee'
and l.ref_oid = emp.id
where
l.last_upd_date >='2013-11-07 15:03:00'
and l.last_upd_date<='2013-11-08 16:00:00'
and l2.is_deleted = 0
union
select
l2.id
from
log l
inner join
log3 l3
on l.ref_table = 'EmpCertificate'
and l.ref_oid = l3.id
inner join
log2 l2
on l2.id = l3.emp_id
where
l.last_upd_date >='2013-11-07 15:03:00'
and l.last_upd_date<='2013-11-08 16:00:00'
and l2.is_deleted = 0