問:數據庫中最多見的慢查詢優化方式是什麼? 答:加索引。 問:爲何加索引能優化慢查詢? 答1:...不知道 答2:由於索引其實就是一種優化查詢的數據結構,好比Mysql中的索引是用B+樹實現的,而B+樹就是一種數據結構,能夠優化查詢速度,能夠利用索引快速查找數據,因此能優化查詢。 問:你知道哪些數據結構能夠提升查詢速度?(聽到這個問題就感受此處有坑...) 答:哈希表、徹底平衡二叉樹、B樹、B+樹等等。 問:那這些數據結構既然都能優化查詢速度,那Mysql種爲什麼選擇使用B+樹? 答:...不知道mysql
SHOW INDEX FROM employees.titles
; 面試
select * from employees.titles where emp_no = 1
select * from employees.titles where title = '1'
select * from employees.titles where emp_no = '1' and title = 1;
select * from employees.titles where title = '1' and emp_no = 1;
複製代碼
到底什麼是索引(Index)? 大學老師是這麼定義的:索引就像書的目錄 Mysql官網是這麼定義的:Indexes are used to find rows with specific column values quickly 我是這麼定義的:索引是一種優化查詢的數據結構算法
爲何哈希表、徹底平衡二叉樹、B樹、B+樹均可以優化查詢,爲什麼Mysql獨獨喜歡B+樹?sql
哈希表(Hash table,也叫散列表),是根據鍵值(Key value)而直接進行訪問的數據結構。也就是說,它經過把鍵值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫作散列函數,存放記錄的數組叫作散列表。數據庫
哈希表的作法其實很簡單,就是把Key經過一個固定的算法函數既所謂的哈希函數轉換成一個整型數字,而後就將該數字對數組長度進行取餘,取餘結果就看成數組的下標,將value存儲在以該數字爲下標的數組空間裏。而當使用哈希表進行查詢的時候,就是再次使用哈希函數將key轉換爲對應的數組下標,並定位到該空間獲取value,如此一來,就能夠充分利用到數組的定位性能進行數據定位。數組
哈希表的特色是什麼? 假若有這麼一張表(表名:sanguo):數據結構
如今對name字段創建哈希索引:函數
注意字段值所對應的數組下標是哈希算法隨機算出來的,因此可能出現哈希衝突。 那麼對於這樣一個索引結構,如今來執行下面的sql語句:性能
select * from sanguo where name = '周瑜'
複製代碼
能夠直接對‘周瑜’按哈希算法算出來一個數組下標,而後能夠直接從數據中取出數據並拿到鎖對應那一行數據的地址,進而查詢那一行數據。 那麼若是如今執行下面的sql語句:優化
select * from sanguo where name > '周瑜'
複製代碼
則無能爲力,由於哈希表的特色就是能夠快速的精確查詢,可是不支持範圍查詢。
仍是上面的表數據用徹底平衡二叉樹表示以下圖(爲了簡單,數據對應的地址就不畫在圖中了。):
圖中的每個節點實際上應該有四部分: 1.左指針,指向左子樹 2.鍵值 3.鍵值所對應的數據的存儲地址 4.右指針,指向右子樹
另外須要提醒的是,二叉樹是有順序的,簡單的說就是「左邊的小於右邊的」 假如咱們如今來查找‘周瑜’,須要找2次(第一次曹操,第二次周瑜),比哈希表要多一次。並且因爲徹底平衡二叉樹是有序的,因此也是支持範圍查找的。
仍是上面的表數據用B樹表示以下圖(爲了簡單,數據對應的地址就不畫在圖中了。):
咱們能夠發現一樣的元素,B樹的表示要比徹底平衡二叉樹要「矮」,緣由在於B樹中的一個節點能夠存儲多個元素。
仍是上面的表數據用B+樹表示以下圖(爲了簡單,數據對應的地址就不畫在圖中了。):
咱們能夠發現一樣的元素,B+樹的表示要比B樹要「胖」,緣由在於B+樹中的非葉子節點會冗餘一份在葉子節點中,而且葉子節點之間用指針相連。
這裏咱們用「反證法」,假如咱們如今就用徹底平衡二叉樹做爲索引的數據結構,咱們來看一下有什麼不妥的地方。
實際上,索引也是很「大」的,由於索引也是存儲元素的,咱們的一個表的數據行數越多,那麼對應的索引文件其實也是會很大的,實際上也是須要存儲在磁盤中的,而不能所有都放在內存中,因此咱們在考慮選用哪一種數據結構時,咱們能夠換一個角度思考,哪一個數據結構更適合從磁盤中讀取數據,或者哪一個數據結構可以提升磁盤的IO效率。 回頭看一下徹底平衡二叉樹,當咱們須要查詢「張飛」時,須要如下步驟
1.從磁盤中取出「曹操」到內存,CPU從內存取出數據進行筆記,「張飛」<「曹操」,取左子樹(產生了一次磁盤IO) 2.從磁盤中取出「周瑜」到內存,CPU從內存取出數據進行筆記,「張飛」>「周瑜」,取右子樹(產生了一次磁盤IO) 3.從磁盤中取出「孫權」到內存,CPU從內存取出數據進行筆記,「張飛」>「孫權」,取右子樹(產生了一次磁盤IO) 4.從磁盤中取出「黃忠」到內存,CPU從內存取出數據進行筆記,「張飛」=「張飛」,找到結果(產生了一次磁盤IO)
同理,回頭看一下B樹,咱們發現只發送三次磁盤IO就能夠找到「張飛」了,這就是B樹的優勢:一個節點能夠存儲多個元素,相對於徹底平衡二叉樹因此整棵樹的高度就下降了,磁盤IO效率提升了。 而,B+樹是B樹的升級版,只是把非葉子節點冗餘一下,這麼作的好處是爲了提升範圍查找的效率。
因此,到這裏,咱們能夠總結出來,Mysql選用B+樹這種數據結構做爲索引,能夠提升查詢索引時的磁盤IO效率,而且能夠提升範圍查詢的效率,而且B+樹裏的元素也是有序的。
這裏有必要先來了解一下磁盤IO的原理。
機械硬盤
固態硬盤
從上面的原理咱們也能知道,固態硬盤比機械硬盤快的最根本最簡單的緣由就是:固態硬盤使用的電路進行讀寫,而機械硬盤使用的機械運動。 其實不論是機械硬盤仍是固態硬盤都是存儲介質,真正控制讀寫的是操做系統。
一個磁盤由大小相同且同軸的圓形盤片組成,磁盤能夠轉動(各個磁盤必須同步轉動)。在磁盤的一側有磁頭支架,磁頭支架固定了一組磁頭,每一個磁頭負責存取一個磁盤的內容。磁頭不能轉動,可是能夠沿磁盤半徑方向運動(實際是斜切向運動),每一個磁頭同一時刻也必須是同軸的,即從正上方向下看,全部磁頭任什麼時候候都是重疊的(不過目前已經有多磁頭獨立技術,可不受此限制)。
磁盤片結構圖
盤片被劃分紅一系列同心環,圓心是盤片中心,每一個同心環叫作一個磁道,全部半徑相同的磁道組成一個柱面。磁道被沿半徑線劃分紅一個個小的段,每一個段叫作一個扇區,每一個扇區是磁盤的最小存儲單元,大小通常爲521字節。
當須要從磁盤讀取數據時,系統會將數據邏輯地址傳給磁盤,磁盤的控制電路按照尋址邏輯將邏輯地址翻譯成物理地址,即肯定要讀的數據在哪一個磁道,哪一個扇區。爲了讀取這個扇區的數據,須要將磁頭放到這個扇區上方,爲了實現這一點,磁頭須要移動對準相應磁道,這個過程叫作尋道,所耗費時間叫作尋道時間,而後磁盤旋轉將目標扇區旋轉到磁頭下,這個過程耗費的時間叫作旋轉時間。
固態硬盤(Solid State Drives),用固態電子存儲芯片陣列而製成的硬盤,由控制單元和存儲單元(FLASH芯片、DRAM芯片)組成。固態硬盤在接口的規範和定義、功能及使用方法上與普通硬盤的徹底相同,在產品外形和尺寸上也徹底與普通硬盤一致。
每一個SSD都有一個控制器(controller)將存儲單元鏈接到電腦,主控器能夠經過若干個通道(channel)並行操做多塊存儲單元
一個Flash Page由兩個或者多個Die(又稱chips組成),這些Dies能夠共享I/0數據總線和一些控制信號線。一個Die又能夠分爲多個Plane,而每一個Plane又包含多個多個Block,每一個Block又分爲多個Page。以Samsung 4GB Flash爲例,一個4GB的Flash Page由兩個2GB的Die組成,共享8位I/0數據總線和一些控制信號線。每一個Die由4個Plane組成,每一個Plane包含2048個Block,每一個Block又包含64個4KB大小的Page
Host是經過LBA(Logical BlockAddress,邏輯地址塊)訪問SSD的,每一個LBA表明着一個Sector(通常爲512B大小),操做系統通常以4K爲單位訪問SSD,咱們把Host訪問SSD的基本單元叫用戶頁(Host Page)。而在SSD內部,SSD主控與Flash之間是Flash Page爲基本單元訪問Flash的,咱們稱Flash Page爲物理頁(Physical Page)。Host每寫入一個Host Page, SSD主控會找一個Physical Page把Host數據寫入,SSD內部同時記錄了這樣一條映射(Map)。有了這樣一個映射關係後,下次Host須要讀某個Host Page 時,SSD就知道從Flash的哪一個位置把數據讀取上來。
計算機科學中著名的局部性原理:當一個數據被用到時,其附近的數據也一般會立刻被使用。 因此操做系統爲了提升效率,讀取數據時每每不是嚴格按需讀取,而是每次都會預讀,即便只須要一個字節,操做系統也會從這個位置開始,順序向後讀取必定長度的數據放入內存。這裏的必定長度叫作頁,也就是操做系統操做磁盤時的基本單位。通常操做系統中一頁的大小是4Kb。
因此,回到咱們的問題,B+樹中一個節點到底存多少個元素合適?,其實也能夠換個角度來思考B+樹中一個節點到底多大合適? 答案是:B+樹中一個節點爲一頁或頁的倍數最爲合適。由於若是一個節點的大小小於1頁,那麼讀取這個節點的時候其實也會讀出1頁,形成資源的浪費;若是一個節點的大小大於1頁,好比1.2頁,那麼讀取這個節點的時候會讀出2頁,也會形成資源的浪費;因此爲了避免形成浪費,因此最後把一個節點的大小控制在1頁、2頁、3頁、4頁等倍數頁大小最爲合適。
那麼,Mysql中B+樹的一個節點大小爲多大呢? 這個問題的答案是「1頁」,這裏說的「頁」是Mysql自定義的單位(其實和操做系統相似),Mysql的Innodb引擎中一頁的默認大小是16k(若是操做系統中一頁大小是4k,那麼Mysql中1頁=操做系統中4頁),可使用命令SHOW GLOBAL STATUS like 'Innodb_page_size';查看。而且還能夠告訴你的是,一個節點爲1頁就夠了。
爲何一個節點爲1頁(16k)就夠了? 解決這個問題,咱們先來看一下Mysql中利用B+樹的具體實現。
一般咱們認爲B+樹的非葉子節點不存儲數據,只有葉子節點才存儲數據;而B樹的非葉子和葉子節點都會存儲數據,會致使非葉子節點存儲的索引值會更少,樹的高度相對會比B+樹高,平均的I/O效率會比較低,因此使用B+樹做爲索引的數據結構,再加上B+樹的葉子節點之間會有指針相連,也方便進行範圍查找。 上圖的data區域兩個存儲引擎會有不一樣。
MYISAM中葉子節點的數據區域存儲的是數據記錄的地址
MyISAM存儲引擎在使用索引查詢數據時,會先根據索引查找到數據地址,再根據地址查詢到具體的數據。而且主鍵索引和輔助索引沒有太多區別。
InnoDB中的B+樹 InnoDB中主鍵索引的葉子節點的數據區域存儲的是數據記錄,輔助索引存儲的是主鍵值
主鍵索引
輔助索引
Innodb中的主鍵索引和實際數據時綁定在一塊兒的,也就是說Innodb的一個表必定要有主鍵索引,若是一個表沒有手動創建主鍵索引,Innodb會查看有沒有惟一索引,若是有則選用惟一索引做爲主鍵索引,若是連惟一索引也沒有,則會默認創建一個隱藏的主鍵索引(用戶不可見)。 另外,Innodb的主鍵索引要比MyISAM的主鍵索引查詢效率要高(少一次磁盤IO),而且比輔助索引也要高不少。 因此,咱們在使用Innodb做爲存儲引擎時,咱們最好: 1.手動創建主鍵索引 2.儘可能利用主鍵索引查詢
回到咱們的問題:爲何一個節點爲1頁(16k)就夠了? 對着上面Mysql中Innodb中對B+樹的實際應用(主要看主鍵索引),咱們能夠發現,B+樹中的一個節點存儲的內容是: 非葉子節點:主鍵+指針 葉子節點:數據
那麼,假設咱們一行數據大小爲1K,那麼一頁就能存16條數據,也就是一個葉子節點能存16條數據; 再看非葉子節點,假設主鍵ID爲bigint類型,那麼長度爲8B,指針大小在Innodb源碼中爲6B,一共就是14B,那麼一頁裏就能夠存儲16K/14=1170個(主鍵+指針),那麼一顆高度爲2的B+樹能存儲的數據爲:117016=18720條,一顆高度爲3的B+樹能存儲的數據爲:11701170*16=21902400(千萬級條)。因此在InnoDB中B+樹高度通常爲1-3層,它就能知足千萬級的數據存儲。在查找數據時一次頁的查找表明一次IO,因此經過主鍵索引查詢一般只須要1-3次IO操做便可查找到數據。因此也就回答了咱們的問題,1頁=16k這麼設置是比較合適的,是適用大多數的企業的,固然這個值是能夠修改的,因此也能根據業務的時間狀況進行調整。
咱們模擬數據創建一個聯合索引
select *, concat(right(emp_no,1), "-", right(title,1), "-", right(from_date,2)) from employees.titles limit 10;
複製代碼
那麼對應的B+樹爲
咱們判斷一個查詢條件能不能用到索引,咱們要分析這個查詢條件能不能利用某個索引縮小查詢範圍
對於select * from employees.titles where emp_no = 1
是能用到索引的,由於它能利用上面的索引全部查詢範圍,首先和第一個節點「4-r-01」比較,1<4,因此能夠直接肯定結果在左子樹,同理,依次按順序進行比較,逐步能夠縮小查詢範圍。
對於select * from employees.titles where title = '1'
是不能用到索引的,由於它不能用到上面的因此,和第一節點進行比較時,沒有emp_no這個字段的值,不能肯定到底該去左子樹仍是右子樹繼續進行查詢。 對於select * from employees.titles where title = '1' and emp_no = 1是能用到索引,按照咱們的上面的分析,先用title='1'這個條件和第一個節點進行比較,是沒有結果的,可是mysql會對這個sql進行優化,優化以後會將emp_no=1這個條件放到第一位,從而能夠利用索引。