引子mysql
什麼是索引算法
爲何須要索引sql
使用索引數據庫
索引優化原理數據結構
正確使用索引測試
在關係數據庫中,索引是一種單獨的、物理層面的對數據庫表中一列或多列的值進行排序的一種存儲結構; 也稱之爲key
大數據
有如下幾種:優化
unique key操作系統
primary key設計
index key
索引的做用至關於圖書的目錄,能夠根據目錄中的頁碼快速找到所需的內容。
思考:一個項目正常運行後,對數據庫的操做中,哪些操做是最頻繁的?
對數據庫的寫操做(增長 刪除 修改)頻繁嗎?
對數據庫的讀操做(查詢)頻繁嗎?
相比較下,對數據的讀操做會更加頻繁,比例在10:1左右,也就是說對數據庫的查詢操做是很是頻繁的
隨着時間的推移,表中的記錄會愈來愈多,此時若是查詢速度太慢的話對用戶體驗是很是不利的
索引是提高查詢效率最有效的手段!
簡單的說索引的就是用幫咱們加快查詢速度的
須要注意的是:在數據庫中插入數據會引起索引的重建
既然索引如此神奇,那之後只要速度慢了就加索引,
這種想法是很是low的,
索引是否是越多越好,而且有了索引後還要考慮索引是否命中
加上索引後對數據的寫操做速度會下降
來看一個例子:
初版的新華字典共800頁,那時沒有檢字表,每一個字的詳細信息,隨機的羅列在書中,一同窗買回來查了一次,在也沒用過,由於沒有任何的數據結構,查字只能一頁一頁日後翻,反了兩小時沒翻着,只能放棄了!
後來出版社發現了這個問題,他們將書中全部字按照拼音音節順序進行了排序,拼音首字母爲a的排在最前,首字母爲z的排在最後:
如此一來再再也不須要一頁一頁的去查字了,而是先查看索引,找出字的拼音首字母到索引中進行對照,例如:找搭
字其拼音首字母爲d,因此直接找到D對應的索引目錄,很快就能定位到要找的搭
字在79頁,查詢速度獲得數量級的提高!
須要注意的是,原來內容爲800頁如今由於多了索引數據,總體頁數必然增長了
數據庫中的索引,實現思路與字典是一致的,須要一個獨立的存儲結構,專門存儲索引數據
本質上索引是經過不斷的縮小查詢範圍來提升查詢效率
數據庫的數據最終存儲到了硬盤上
機械硬盤因爲設計原理,致使查找數據時須要有一個尋道時間與平均延遲時間,常規硬盤尋道爲5ms,平均延遲按照每分鐘7200轉來計算,7200/60 = 120 ; 1000/120/2 = 4ms 總共爲9ms,那麼9毫秒對於cpu而言已經很是很是的長了,足夠作不少運算操做,目前最新的處理器每秒能處理數萬億次運算,拿一個很是垃圾的處理器來舉例子,假設處理器每秒處理5億次計算,每毫秒是50萬次運算,9ms能夠進行450萬次運算,數據庫中成千上萬的數據,每條數據9ms顯然慢到不行!
考慮到磁盤IO是很是高昂的操做,計算機操做系統作了一些優化,當一次IO時,不光把當前磁盤地址的數據,而是把相鄰的數據也都讀取到內存緩衝區內,由於局部預讀性原理告訴咱們,當計算機訪問一個地址的數據的時候,與其相鄰的數據也會很快被訪問到。每一次IO讀取的數據咱們稱之爲一頁(page)。具體一頁有多大數據跟操做系統有關,通常爲4k或8k,也就是咱們讀取一頁內的數據時候,實際上才發生了一次IO,這個理論對於索引的數據結構設計很是有幫助。
在字典的例子中咱們知道了,索引是獨立於真實數據的一個存儲結構,這個結構究竟是什麼樣的?
索引最終的目的是要儘量下降io次數,減小查找的次數,以最少的io找到須要的數據,此時B+樹閃亮登場
光有數據結構還不行,還須要有對應的算法作支持,就是二分查找法
有了B+數據結構後查找數據的方式就再也不是逐個的對比了,而是經過二分查找法來查找(流程演示)
另外,其實大多數文件系統都是使用B+是來完成的!
經過分析能夠發如今上面的樹中,查找一個任何一個數據都是3次IO操做, 可是這個3次並非固定的,它取決於樹結構的高度,目前是三層,若是要存儲新的數據比99還大的數據時,發現葉子節點已經不夠了必須在上面加一個子節點,因爲樹根只能有一個因此,整個數的高度會增長,一旦高度增長則 查找是IO次數也會增長,因此:
應該儘量的將數據量小的字段做爲索引,這樣一個葉子節點能存儲的數據就更多,從而下降樹的高度;
例如:name
和id
,應當將id設置爲索引而不是name
當b+樹的數據項是複合的數據結構,好比(name,age,sex)的時候(多字段聯合索引),b+樹會按照從左到右的順序來創建搜索樹,好比當(張三,20,F)這樣的數據來檢索的時候,b+樹會優先比較name來肯定下一步的所搜方向,若是name相同再依次比較age和sex,最後獲得檢索的數據;但當(20,F)這樣的沒有name的數據來的時候,b+樹就不知道下一步該查哪一個節點,由於創建搜索樹的時候name就是第一個比較因子,必需要先根據name來搜索才能知道下一步去哪裏查詢。好比當(張三,F)這樣的數據來檢索時,b+樹能夠用name來指定搜索方向,但下一個字段age的缺失,因此只能把名字等於張三的數據都找到,而後再匹配性別是F的數據了, 這個是很是重要的性質,即索引的最左匹配特性。
mysql官方文檔原文: 插入瞭解 或摺疊
MySQL爲表把它的數據詞典信息以.frm文件的形式存在數據庫目錄裏,這對全部MySQL存儲引擎都是真的。但 是每一個InnoDB表在表空間內的InnoDB內部數據詞典裏有它本身的條目。當MySQL移除表或數據庫,它不得不 刪除.frm文件和InnoDB數據詞典內的相應條目。這就是爲何你不能在數據庫之間簡單地移動.frm文件來移 動InnoDB表。
每一個InnoDB表有專門索引,被稱爲clustered index,對行的數據被存於其中。若是你對你的表定義一 個PRIMARY KEY, 主鍵的索引是集束索引。
若是你沒有爲表定義PRIMARY KEY,MySQL拾取第一個僅有NOT NULL列的UNIQUE索引做爲主鍵,並 且InnoDB把它看成集束索引來用。若是表中沒有這樣一個索引,InnoDB內部產生一個集束索引,其中 用InnoDB在這樣一個表內指定給行的行ID來排序行。行ID是一個6字節的域,它在新行被插入的時候簡單地增長。所以被行ID排序的行是物理地按照插入順序排的。
經過集束索引訪問一個行是較快的,由於行數據是在索引搜索引導的同一頁面。若是表是巨大的,當對比於傳 統解決方案,集束索引構架常常節約磁盤I/O。(在許多數據庫,數據傳統地被存在與索引記錄不一樣的頁)。
在InnoDB中,非集束索引裏的記錄(也稱爲第二索引)包含對應行的主鍵值。InnoDB用這個 主鍵值來從集束索 引中搜索行。注意,若是主鍵是長的,第二索引使用更多空間。
簡單總結:
聚焦索引的特色:
葉子節點保存的就是完整的一行記錄,若是設置了主鍵,主鍵就做爲彙集索引,
若是沒有主鍵,則找第一個NOT NULL 且QUNIQUE的列做爲彙集索引,
若是也沒有這樣的列,innoDB會在表內自動產生一個彙集索引,它是自增的
彙集索引中包含了完整的記錄
除了彙集索引以外的索引都稱之爲輔助索引或第二索引,包括 foreign key
與 unique
輔助索引的特色:
其葉子節點保存的是索引數據與所在行的主鍵值,InnoDB用這個 主鍵值來從彙集索引中搜查找數據
覆蓋索引
覆蓋索引指的是須要的數據僅在輔助索引中就能找到:
#假設stu表的name字段是一個輔助索引 select name from stu where name = "jack";
這樣的話則不須要在查找彙集索引數據已經找到
回表
若是要查找的數據在輔助索引中不存在,則須要回到彙集索引中查找,這種現象稱之爲回表
# name字段是一個輔助索引 而sex字段不是索引 select sex from stu where name = "jack";
須要從輔助索引中獲取主鍵的值,在拿着主鍵值到彙集索引中找到sex的值
查詢速度對比:
彙集索引 > 覆蓋索引 > 非覆蓋索引
案例:
首先準備一張表數據量在百萬級別
create table usr(id int,name char(10),gender char(3),email char(30)); #準備數據 delimiter // create procedure addData(in num int) begin declare i int default 0; while i < num do insert into usr values(i,"jack","m",concat("xxxx",i,"@qq.com")); set i = i + 1; end while; end// delimiter ; #執行查詢語句 觀察查詢時間 select count(*) from usr where id = 1; #1 row in set (3.85 sec) #時間在秒級別 比較慢 1. #添加主鍵 alter table usr add primary key(id); #再次查詢 select count(*) from usr where id = 1; #1 row in set (0.00 sec) #基本在毫秒級就能完成 提高很是大 2. #當條件爲範圍查詢時 select count(*) from usr where id > 1; #速度依然很慢 對於這種查詢沒有辦法能夠優化由於須要的數據就是那麼多 #縮小查詢範圍 速度立馬就快了 select count(*) from usr where id > 1 and id < 10; #當查詢語句中匹配字段沒有索引時 效率測試 select count(*) from usr where name = "jack"; #1 row in set (2.85 sec) # 速度慢 3. # 爲name字段添加索引 create index name_index on usr(name); # 再次查詢 select count(*) from usr where name = "jack"; #1 row in set (3.89 sec) # 速度反而下降了 爲何? #因爲name字段的區分度很是低 徹底沒法區分 ,由於值都相同 這樣一來B+樹會沒有任何的子節點,像一根竹竿每一都匹配至關於,有幾條記錄就有幾回io ,全部要注意 區分度低的字段不該該創建索引,不能加速查詢反而下降寫入效率, #同理 性別字段也不該該創建索引,email字段更加適合創建索引 # 修改查詢語句爲 select count(*) from usr where name = "aaaaaaaaa"; #1 row in set (0.00 sec) 速度很是快由於在 樹根位置就已經判斷出樹中沒有這個數據 所有跳過了 # 模糊匹配時 select count(*) from usr where name like "xxx"; #快 select count(*) from usr where name like "xxx%"; #快 select count(*) from usr where name like "%xxx"; #慢 #因爲索引是比較大小 會從左邊開始匹配 很明顯全部字符都能匹配% 因此全都匹配了一遍 4.索引字段不能參加運算 select count(*) from usr where id * 12 = 120; #速度很是慢緣由在於 mysql須要取出全部列的id 進行運算以後才能判斷是否成立 #解決方案 select count(*) from usr where id = 120/12; #速度提高了 由於在讀取數據時 條件就必定固定了 至關於 select count(*) from usr where id = 10; #速度天然快了 5.有多個匹配條件時 索引的執行順序 and 和 or #先看and #先刪除全部的索引 alter table usr drop primary key; drop index name_index on usr; #測試 select count(*) from usr where name = "jack" and gender = "m" and id = 1 and email = "xxxx2@qq.com"; #1 row in set (1.34 sec) 時間在秒級 #爲name字段添加索引 create index name_index on usr(name); #測試 select count(*) from usr where name = "jack" and gender = "m" and id = 1 and email = "xxxx2@qq.com"; #1 row in set (17.82 sec) 反而時間更長了 #爲gender字段添加索引 create index gender_index on usr(gender); #測試 select count(*) from usr where name = "jack" and gender = "m" and id = 1 and email = "xxxx2@qq.com"; #1 row in set (16.83 sec) gender字段任然不具有區分度 #爲id加上索引 alter table usr add primary key(id); #測試 select count(*) from usr where name = "jack" and gender = "m" and id = 1 and email = "xxxx1@qq.com"; #1 row in set (0.00 sec) id字段區分度高 速度提高 #雖然三個字段都有索引 mysql並非從左往右傻傻的去查 而是找出一個區分度高的字段優先匹配 #改成範圍匹配 select count(*) from usr where name = "jack" and gender = "m" and id > 1 and email = "xxxx1@qq.com"; #速度變慢了 #刪除id索引 爲email創建索引 alter table usr drop primary key; create index email_index on usr(email); #測試 select count(*) from usr where name = "jack" and gender = "m" and id = 1 and email = "xxxx2@qq.com"; #1 row in set (0.00 sec) 速度很是快 #對於or條件 都是從左往右匹配 select count(*) from usr where name = "jackxxxx" or email = "xxxx0@qq.com"; #注意 必須or兩邊都有索引纔會使用索引 and 語句中只要有一個存在索引就能提升速度 6.多字段聯合索引 爲何須要聯合索引 案例: select count(*) from usr where name = "jack" and gender = "m" and id > 3 and email = "xxxx2@qq.com"; 假設全部字段都是區分度很是高的字段,那麼除了id爲誰添加索引都可以提高速度,可是若是sql語句中沒有出現索引字段,那就沒法加速查詢,最簡單的辦法是爲每一個字段都加上索引,可是索引也是一種數據,會佔用內存空間,而且下降寫入效率 此處就可使用聯合索引, 聯合索引最重要的是順序 按照最左匹配原則 應該將區分度高的放在左邊 區分度低的放到右邊 #刪除其餘索引 drop index name_index on usr; drop index email_index on usr; #聯合索引 create index mul_index on usr(email,name,gender,id); # 查詢測試 select count(*) from usr where name = "xx" and id = 1 and email = "xx"; 只要語句中出現了最左側的索引(email) 不管在前在後都能提高效率 drop index mul_index on usr;
1.使用佔用空間最小的字段來做爲索引
2.不要再一行中存儲太多的數據,例如小說,視頻,若是字段太多能夠分表
3.儘可能使用覆蓋查詢
4.若是字段區分度低(重複度高),創建索引是沒有意義,反過來講應該將區分度高的字段做爲索引
5.模糊匹配中,百分號儘可能不要寫在前面
6.不要再等號的左邊作運算
例如:select count() from usr where id 3 = 6; 也會遍歷全部記錄
7.and語句中會自動找一個具有縮印的字段優先執行,因此咱們應該在and語句中至少包含一個具有索引的字段
8.or語句要避免使用,若是要用則保證全部字段都有索引才能加速
9.聯合索引中,順序應該將區分度最高的放到左邊,最低的放右邊,
查詢語句中必須保證最左邊的索引出如今語句中
另外須要注意:若是要查詢的數據量很是大 索引沒法加速
總結: 不是添加了索引就能提速,須要考慮索引添加的是否合理,sql語句是否使用到了索引
額外的優化手段:
1.explain 查詢sql的執行計劃 找出全表掃描發生的緣由 ,從而修改sql語句
2.啓用慢查詢記錄,設置最大時間,將執行時間過長的sql記錄到日誌中,從而加以分析