常常有同窗問我,個人一個SQL語句使用了索引,爲何仍是會進入到慢查詢之中呢?今天咱們就從這個問題開始來聊一聊索引和慢查詢。程序員
另外插入一個題外話,我的認爲團隊要合理的使用ORM,能夠參考 ORM的權衡和抉擇。合理利用的是ORM在面向對象和寫操做方面的優點,避免聯合查詢上可能產生的坑(固然若是你的Linq查詢能力很強另當別論),由於ORM屏蔽了太多的DB底層的知識內容,對程序員不是件好事,對性能有極致追求,可是ORM理解不透徹的團隊更加要謹慎。面試
言歸正傳,爲了實驗,我建立了以下表:數據庫
CREATE TABLE `T`( `id` int(11) NOT NULL, `a` int(11) DEFAUT NULL, PRIMARY KEY(`id`), KEY `a`(`a`) ) ENGINE=InnoDB;
該表有三個字段,其中用id是主鍵索引,a是普通索引。設計模式
首先SQL判斷一個語句是否是慢查詢語句,用的是語句的執行時間。他把語句執行時間跟long_query_time這個系統參數做比較,若是語句執行時間比它還大,就會把這個語句記錄到慢查詢日誌裏面,這個參數的默認值是10秒。固然在生產上,咱們不會設置這麼大,通常會設置1秒,對於一些比較敏感的業務,可能會設置一個比1秒還小的值。性能
語句執行過程當中有沒有用到表的索引,能夠經過explain一個語句的輸出結果來看KEY的值不是NULL。學習
咱們看下 explain select * from t;
的KEY結果是NULL優化
(圖一)設計
explain select * from t where id=2;
的KEY結果是PRIMARY,就是咱們常說的使用了主鍵索引日誌
(圖二)code
explain select a from t;
的KEY結果是a,表示使用了a這個索引。
(圖三)
雖而後兩個查詢的KEY都不是NULL,可是最後一個實際上掃描了整個索引樹a。
假設這個表的數據量有100萬行,圖二的語句仍是能夠執行很快,可是圖三就確定很慢了。若是是更極端的狀況,好比,這個數據庫上CPU壓力很是的高,那麼可能第2個語句的執行時間也會超過long_query_time,會進入到慢查詢日誌裏面。
因此咱們能夠得出一個結論:是否使用索引和是否進入慢查詢之間並無必然的聯繫。使用索引只是表示了一個SQL語句的執行過程,而是否進入到慢查詢是由它的執行時間決定的,而這個執行時間,可能會受各類外部因素的影響。換句話來講,使用了索引你的語句可能依然會很慢。
那若是咱們在更深層次的看這個問題,其實他還潛藏了一個問題須要澄清,就是什麼叫作使用了索引。
咱們都知道,InnoDB是索引組織表,全部的數據都是存儲在索引樹上面的。好比上面的表t,這個表包含了兩個索引,一個主鍵索引和一個普通索引。在InnoDB裏,數據是放在主鍵索引裏的。如圖所示:
能夠看到數據都放在主鍵索引上,若是從邏輯上說,全部的InnoDB表上的查詢,都至少用了一個索引,因此如今我問你一個問題,若是你執行select from t where id>0
,你以爲這個語句有用上索引嗎?
咱們看上面這個語句的explain的輸出結果顯示的是PRIMARY。其實從數據上你是知道的,這個語句必定是作了全面掃描。可是優化器認爲,這個語句的執行過程當中,須要根據主鍵索引,定位到第1個知足ID>0的值,也算用到了索引。
因此即便explain的結果裏寫的KEY不是NULL,實際上也多是全表掃描的,所以InnoDB裏面只有一種狀況叫作沒有使用索引,那就是從主鍵索引的最左邊的葉節點開始,向右掃描整個索引樹。
也就是說,沒有使用索引並非一個準確的描述。
你能夠用全表掃描來表示一個查詢遍歷了整個主鍵索引樹;
也能夠用全索引掃描,來講明像select a from t;這樣的查詢,他掃描了整個普通索引樹;
而select * from t where id=2這樣的語句,纔是咱們平時說的使用了索引。他表示的意思是,咱們使用了索引的快速搜索功能,而且有效的減小了掃描行數。
根據以上解剖,咱們知道全索引掃描會讓查詢變慢,接下來就要來談談索引的過濾性。
假設你如今維護了一個表,這個表記錄了中國14億人的基本信息,如今要查出全部年齡在10~15歲之間的姓名和基本信息,那麼你的語句會這麼寫,select * from t_people where age between 10 and 15
。
你一看這個語句必定要在age字段上開始創建索引了,不然就是個全面掃描,可是你會發現,在你創建索引之後,這個語句仍是執行慢,由於知足這個條件的數據可能有超過1億行。
咱們來看看創建索引之後,這個表的組織結構圖:
這個語句的執行流程是這樣的:
從索引上用樹搜索,取到第1個age等於10的記錄,獲得它的主鍵id的值,根據id的值去主鍵索引取整行的信息,做爲結果集的一部分返回;
在索引age上向右掃描,取下一個id的值,到主鍵索引上取整行信息,做爲結果集的一部分返回;
重複上面的步驟,直到碰到第1個age大於15的記錄;
你看這個語句,雖然他用了索引,可是他掃描超過了1億行。因此你如今知道了,當咱們在討論有沒有使用索引的時候,其實咱們關心的是掃描行數。
對於一個大表,不止要有索引,索引的過濾性還要足夠好。
像剛纔這個例子的age,它的過濾性就不夠好,在設計表結構的時候,咱們要讓全部的過濾性足夠好,也就是區分度足夠高。
那麼過濾性好了,是否是表示查詢的掃描行數就必定少呢?
咱們再來看一個例子:
若是你的執行語句是 select * from t_people where name='張三' and age=8
t_people表上有一個索引是姓名和年齡的聯合索引,那這個聯合索引的過濾性應該不錯,能夠在聯合索引上快速找到第1個姓名是張三,而且年齡是8的小朋友,固然這樣的小朋友應該很少,所以向右掃描的行數不多,查詢效率就很高。
可是查詢的過濾性和索引的過濾性可不必定是同樣的,若是如今你的需求是查出全部名字的第1個字是張,而且年齡是8歲的全部小朋友,你的語句會怎麼寫呢?
你的語句要怎麼寫?很顯然你會這麼寫:select * from t_people where name like '張%' and age=8;
在MySQL5.5和以前的版本中,這個語句的執行流程是這樣的:
首先從聯合索引上找到第1個年齡字段是張開頭的記錄,取出主鍵id,而後到主鍵索引樹上,根據id取出整行的值;
判斷年齡字段是否等於8,若是是就做爲結果集的一行返回,若是不是就丟棄。
在聯合索引上向右遍歷,並重復作回表和判斷的邏輯,直到碰到聯合索引樹上名字的第1個字不是張的記錄爲止。
咱們把根據id到主鍵索引上查找整行數據這個動做,稱爲回表。你能夠看到這個執行過程裏面,最耗費時間的步驟就是回表,假設全國名字第1個字是張的人有8000萬,那麼這個過程就要回表8000萬次,在定位第一行記錄的時候,只能使用索引和聯合索引的最左前綴,最稱爲最左前綴原則。
你能夠看到這個執行過程,它的回表次數特別多,性能不夠好,有沒有優化的方法呢?
在MySQL5.6版本,引入了index condition pushdown的優化。咱們來看看這個優化的執行流程:
首先從聯合索引樹上,找到第1個年齡字段是張開頭的記錄,判斷這個索引記錄裏面,年齡的值是否是8,若是是就回表,取出整行數據,做爲結果集的一部分返回,若是不是就丟棄;
在聯合索引樹上,向右遍歷,並判斷年齡字段後,根據須要作回表,直到碰到聯合索引樹上名字的第1個字不是張的記錄爲止;
這個過程跟上面的差異,是在遍歷聯合索引的過程當中,將年齡等於8的條件下推到全部遍歷的過程當中,減小了回表的次數,假設全國名字第1個字是張的人裏面,有100萬個是8歲的小朋友,那麼這個查詢過程當中在聯合索引裏要遍歷8000萬次,而回表只須要100萬次。
能夠看到這個優化的效果仍是很不錯的,可是這個優化仍是沒有繞開最左前綴原則的限制,所以在聯合索引你仍是要掃描8000萬行,那有沒有更進一步的優化方法呢?
咱們能夠考慮把名字的第一個字和age來作一個聯合索引。這裏可使用MySQL5.7引入的虛擬列來實現。對應的修改表結構的SQL語句:
alter table t_people add name_first varchar(2) generated (left(name,1)),add index(name_first,age);
咱們來看這個SQL語句的執行效果:
CREATE TABLE `t_people`( `id` int(11) DEFAULT NULL, `name` varchar(20) DEFAUT NULL, `name_first` varchar(2) GENERATED ALWAYS AS (left(`name`,1)) VIRTUAL,KEY `name_first`(`name_first`,'age') ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
首先他在people上建立一個字段叫name_first的虛擬列,而後給name_first和age上建立一個聯合索引,而且,讓這個虛擬列的值老是等於name字段的前兩個字節,虛擬列在插入數據的時候不能指定值,在更新的時候也不能主動修改,它的值會根據定義自動生成,在name字段修改的時候也會自動修改。
有了這個新的聯合索引,咱們在找名字的第1個字是張,而且年齡爲8的小朋友的時候,這個SQL語句就能夠這麼寫:select * from t_people where name_first='張' and age=8。
這樣這個語句的執行過程,就只須要掃描聯合索引的100萬行,並回表100萬次,這個優化的本質是咱們建立了一個更緊湊的索引,來加速了查詢的過程。
本文給你介紹了索引的基本結構和一些查詢優化的基本思路,你如今知道了,使用索引的語句也有多是慢查詢,咱們的查詢優化的過程,每每就是減小掃描行數的過程。
慢查詢概括起來大概有這麼幾種狀況:
假設業務要求的就是要統計年齡在10-15歲的14億人的數量,不能增長過濾因子,那該怎麼辦?(select * from t_people where age between 10 and 15
)
假設該統計必須是OLTP,實時展現統計數據,又該怎麼解決?
歡迎關注公衆號 【碼農開花】一塊兒學習成長 我會一直分享Java乾貨,也會分享免費的學習資料課程和麪試寶典 回覆:【計算機】【設計模式】【面試】有驚喜哦