1. 程式人生 > 其它 >SpringBoot上傳圖片與回顯

SpringBoot上傳圖片與回顯

目錄

13、索引原理與慢查詢優化

索引是儲存引擎中一種資料結構,或者說資料的組織方式,又稱之為鍵key,是儲存引擎用於快速找到記錄的一種資料結構。 為資料建立索引就好比是為書建目錄,或者說是為字典建立音序表,如果要查某個字,如果不使用音序表,則需要從幾百頁中逐頁去查。

使用索引的好處

一般的應用系統,讀寫比例在9:1左右,而且插入操作和一般的更新操作很少出現效能問題,在生產環境中,我們遇到最多的、也是最容易出問題的,還是一些複雜的查詢操作,因此對查詢語句的優化顯然是重中之重。說起加速查詢,就不得不提到索引了。 索引優化應該是對查詢效能優化最有效的手段了。索引能夠輕易將查詢效能提高好幾個數量級。

理解索引的儲備知識

要了解索引的資料結構我來先來儲備一些知識。

儲備知識1:機械磁碟一次IO的時間

機械磁碟一次io的時間 = 尋道時間 + 旋轉延遲 + 傳輸時間

# 尋道時間
道時間指的是磁臂移動到指定磁軌所需要的時間,主流磁碟一般在5ms以下

# 旋轉延遲
旋轉延遲就是我們經常聽說的磁碟轉速,比如一個磁碟7200轉,表示每分鐘能轉7200次,也就是說1秒鐘能轉120次,旋轉延遲就是1/120/2 = 4.17ms;

# 傳輸時間
傳輸時間指的是從磁碟讀出或將資料寫入磁碟的時間,一般在零點幾毫秒,相對於前兩個時間可以忽略不計

所以訪問一次磁碟的時間,即一次磁碟IO的時間約等於5+4.17 = 9ms左右

這9ms對於人來說可能非常短,但對於計算機來可是非常長的一段時間,長到什麼程度呢?
一臺500 -MIPS(Million Instructions Per Second)的機器每秒可以執行5億條指令,因為指令依靠的是電的性質,換句話說執行一次IO的時間可以執行約450萬條指令,資料庫動輒十萬百萬乃至千萬級資料,每次9毫秒的時間,顯然是個災難。

儲備知識2:磁碟的預讀

# 考慮到磁碟IO是非常高昂的操作,計算機作業系統做了一些優化:
當一次IO時,不光把當前磁碟地址的資料,而是把相鄰的資料也都讀取到記憶體緩衝區內,因為區域性預讀性原理告訴我們,當計算機訪問一個地址的資料的時候,與其相鄰的資料也會很快被訪問到。每一次IO讀取的資料我們稱之為一頁(page)。具體一頁有多大資料跟作業系統有關,一般為4k或8k,也就是我們讀取一頁內的資料時候,實際上才發生了一次IO,這個理論對於索引的資料結構設計非常有幫助。

儲備知識3:索引原理精髓提煉

索引的目的在於提高查詢效率,與我們查閱圖書所用的目錄是一個道理:先定位到章,然後定位到該章下的一個小節,然後找到頁數。相似的例子還有:查字典,查火車車次,飛機航班等

!!!!!!!!!!!!!!!!!!!!!!!!!!!!
本質都是:
通過不斷地縮小想要獲取資料的範圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是說,有了這種索引機制,我們可以總是用同一種查詢方式來鎖定資料。
!!!!!!!!!!!!!!!!!!!!!!!!!!!!

資料庫也是一樣,但顯然要複雜的多,因為不僅面臨著等值查詢,還有範圍查詢(>、<、between、in)、模糊查詢(like)、並集查詢(or)等等。資料庫應該選擇怎麼樣的方式來應對所有的問題呢?我們回想字典的例子,能不能把資料分成段,然後分段查詢呢?最簡單的如果1000條資料,1到100分成第一段,101到200分成第二段,201到300分成第三段......這樣查第250條資料,只要找第三段就可以了,一下子去除了90%的無效資料。但如果是1千萬的記錄呢,分成幾段比較好?稍有演算法基礎的同學會想到搜尋樹,其平均複雜度是lgN,具有不錯的查詢效能。但這裡我們忽略了一個關鍵的問題,複雜度模型是基於每次相同的操作成本來考慮的。而資料庫實現比較複雜,一方面資料是儲存在磁碟上的,另外一方面為了提高效能,每次又可以把部分資料讀入記憶體來計算,因為我們知道訪問磁碟的成本大概是訪問記憶體的十萬倍左右,所以簡單的搜尋樹難以滿足複雜的應用場景。

索引分類

索引模型分為很多種類

#===========B+樹索引(等值查詢與範圍查詢都快)
二叉樹->平衡二叉樹->B樹->B+樹
        
#===========HASH索引(等值查詢快,範圍查詢慢)
將資料打散再去查詢

#===========FULLTEXT:全文索引 (只可以用在MyISAM引擎)
通過關鍵字的匹配來進行查詢,類似於like的模糊匹配
like + %在文字比較少時是合適的
但是對於大量的文字資料檢索會非常的慢
全文索引在大量的資料面前能比like快得多,但是準確度很低
百度在搜尋文章的時候使用的就是全文索引,但更有可能是ES

不同的儲存引擎支援的索引型別也不一樣

  • InnoDB儲存引擎
    支援事務,支援行級別鎖定,支援 B-tree(預設)、Full-text 等索引,不支援 Hash 索引。

  • MyISAM儲存引擎
    不支援事務,支援表級別鎖定,支援 B-tree、Full-text 等索引,不支援 Hash 索引。

  • Memory儲存引擎
    不支援事務,支援表級別鎖定,支援 B-tree、Hash 等索引,不支援 Full-text 索引。

因為mysql預設的儲存引擎是innodb,而innodb儲存引擎的索引模型/結構是B+樹,所以我們著重介紹B+樹,那麼大家最關注的問題來了:

B+樹索引到底是如何加速查詢的呢?

索引的資料結構

常見的資料結構有5種:二叉查詢樹、平衡二叉樹、B樹以及B+樹。

建立索引的兩大步驟

為某個欄位建立索引,即以某個欄位的值為基礎構建索引結構,那麼如何構建呢?分為兩大步驟

  • 1、提取每行記錄中該欄位的值,以該值當作key,至於key對的value是什麼?每種索引結構各不相同
  • 2、然後以key值為基礎構建索引結構

以後的查詢條件中使用了該欄位,則會命中索引結構。

案例:

# 1、為user表的id欄位建立索引,會以每條記錄的id欄位值為基礎生成索引結構
create index 索引名 on user(id);

使用索引
select * from user where id = xxx;

# 2、為user表的name欄位建立索引,會以每條記錄的name欄位值為基礎生成索引結構
create index 索引名 on user(id);

使用索引
select * from user where name = xxx;

那麼索引的結構到底長什麼樣子,讓其能夠加速查詢呢?

innodb儲存引擎預設的索引結構為B+樹,而B+樹是由二叉樹、平衡二叉樹、B樹再到B+樹一路演變過來的。

二叉查詢樹

二叉查詢樹(Binary Search Tree),也稱有序二叉樹(ordered binary tree),排序二叉樹(sorted binary tree),是指一棵空樹或者具有下列性質的二叉樹:

    1. 若任意節點的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;
    2. 若任意節點的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值;
    1. 任意節點的左、右子樹也分別為二叉查詢樹。
    2. 沒有鍵值相等的節點(no duplicate nodes)。

有user表,我們以id欄位值為基礎建立索引。

1、提取每一條記錄的id值作為key值,value為本行完整記錄,即:

id user
10 zs
7 ls
13 ww
5 zl
8 xw
12 xm
17 dy

2、以key值的大小為基礎構建二叉樹,如上圖所示

二叉查詢樹的特點就是任何節點的左子節點的鍵值都小於當前節點的鍵值,右子節點的鍵值都大於當前節點的鍵值。
頂端的節點我們稱為根節點,沒有子節點的節點我們稱之為葉節點。

如果我們需要查詢id=12的使用者資訊

select * from user where id=12;

利用我們建立的二叉查詢樹索引,查詢流程如下:

  1. 將根節點作為當前節點,把12與當前節點的鍵值10比較,12大於10,接下來我們把當前節點>的右子節點作為當前節點。

  2. 繼續把12和當前節點的鍵值13比較,發現12小於13,把當前節點的左子節點作為當前節點。

  3. 把12和當前節點的鍵值12對比,12等於12,滿足條件,我們從當前節點中取出data,即id=1>2,name=xm。

利用二叉查詢樹我們只需要3次即可找到匹配的資料。如果在表中一條條的查詢的話,我們需要6次才能找到。

平衡二叉樹

基於5.1所示的二叉樹,我們確實可以快速地找到資料。但是讓我們回到二叉查詢樹地特點上,只論二叉查詢樹,它的特點只是:任何節點的左子節點的鍵值都小於當前節點的鍵值,右子節點的鍵值都大於當前節點的鍵值。 所以,依據二叉查詢樹的特點,二叉樹可以是這樣構造的,如圖

這個時候可以看到我們的二叉查詢樹變成了一個連結串列。如果我們需要查詢id=17的使用者資訊,我們需要查詢7次,也就相當於全表掃描了。 導致這個現象的原因其實是二叉查詢樹變得不平衡了,也就是高度太高了,從而導致查詢效率的不穩定。

為了解決這個問題,我們需要保證二叉查詢樹一直保持平衡,就需要用到平衡二叉樹了。平衡二叉樹又稱AVL樹,在滿足二叉查詢樹特性的基礎上,要求每個節點的左右子樹的高度不能超過1。 下面是平衡二叉樹和非平衡二叉樹的對比:

由平衡二叉樹的構造我們可以發現第一張圖中的二叉樹其實就是一棵平衡二叉樹。平衡二叉樹保證了樹的構造是平衡的,當我們插入或刪除資料導致不滿足平衡二叉樹不平衡時,平衡二叉樹會進行調整樹上的節點來保持平衡。具體的調整方式這裡就不介紹了。平衡二叉樹相比於二叉查詢樹來說,查詢效率更穩定,總體的查詢速度也更快。

那麼是不是說基於平衡二叉樹構建索引的結構就可以了呢?答案是否!

B樹

那麼直接用平衡二叉樹這種資料結構來構建索引有什麼問題?

首先,因為記憶體的易失性。一般情況下,我們都會選擇將user表中的資料和索引儲存在磁碟這種外圍裝置中。但是和記憶體相比,從磁碟中讀取資料的速度會慢上百倍千倍甚至萬倍,所以,我們應當儘量減少從磁碟中讀取資料的次數。另外,從磁碟中讀取資料時,都是按照磁碟塊來讀取的,並不是一條一條的讀。 如果我們能把儘量多的資料放進磁碟塊中,那一次磁碟讀取操作就會讀取更多資料,那我們查詢資料的時間也會大幅度降低。所以,如果我們單純用平衡二叉樹這種資料結構作為索引的資料結構,即每個磁碟塊只放一個節點,每個節點中只存放一組鍵值對,此時如果資料量過大,二叉樹的節點則會非常多,樹的高度也隨即變高,我們查詢資料的也會進行很多次磁碟IO,查詢資料的效率也會變得極低!

綜上,如果我們能夠在平衡二叉的樹的基礎上,把更多的節點放入一個磁碟塊中,那麼平衡二叉樹的弊端也就解決了。即構建一個單節點可以儲存多個鍵值對的平衡樹,這就是B樹。

注意:

1、圖中的p節點為指向子節點的指標,二叉查詢樹和平衡二叉樹其實也有,因為圖的美觀性,被省略了。
2、圖中的每個節點裡面放入了多組鍵值對,一個節點也稱為一頁,一頁即一個磁碟塊,在mysql中資料讀取的基本單位都是頁,即一次io讀取一個頁的資料,所以我們這裡叫做頁更符合mysql中索引的底層資料結構。

從上圖可以看出,B樹相對於平衡二叉樹,每個節點儲存了更多的鍵值(key)和資料(data),並且每個節點擁有更多的子節點,子節點的個數一般稱為階,上述圖中的B樹為3階B樹,高度也會很低。 基於這個特性,B樹查詢資料讀取磁碟的次數將會很少,資料的查詢效率也會比平衡二叉樹高很多。 假如我們要查詢id=28的使用者資訊,那麼我們在上圖B樹中查詢的流程如下:

  • 1、先找到根節點也就是頁1,判斷28在鍵值17和35之間,我們那麼我們根據頁1中的指標p2找到頁3。

  • 2、將28和頁3中的鍵值相比較,28在26和30之間,我們根據頁3中的指標p2找到頁8。

  • 3、將28和頁8中的鍵值相比較,發現有匹配的鍵值28,鍵值28對應的使用者資訊為(28,bv)。

注意:

  • 1、B樹的構造是有一些規定的,但這不是本文的關注點,有興趣的同學可以令行了解。
  • 2、B樹也是平衡的,當增加或刪除資料而導致B樹不平衡時,也是需要進行節點調整的。

那麼B樹是否就是索引的最終結構了呢?答案是no,B樹只擅長做等值查詢,而對於範圍查詢(範圍查詢的本質就是n次等值查詢),或者說排序操作,B樹也幫不了我們

select * from user where id=3;  -- 擅長
select * from user where id>3;  -- 不擅長

上帝說要有光,於是有了B+樹。

B+樹

B+樹是對B樹的進一步優化。讓我們先來看下B+樹的結構圖:

根據上圖我們來看下B+樹和B樹有什麼不同。

  1. B+樹非葉子節點non-leaf node上是不儲存資料的,僅儲存鍵,而B樹的非葉子節點中不僅儲存鍵,也會儲存資料。B+樹之所以這麼做的意義在於:樹一個節點就是一個頁,而資料庫中頁的大小是固定的,innodb儲存引擎預設一頁為16KB,所以在頁大小固定的前提下,能往一個頁中放入更多的節點,相應的樹的階數(節點的子節點樹)就會更大,那麼樹的高度必然更矮更胖,如此一來我們查詢資料進行磁碟的IO次數有會再次減少,資料查詢的效率也會更快。

  2. B+樹的階數是等於鍵的數量的,例如上圖,我們的B+樹中每個節點可以儲存3個鍵,3層B+樹存可以儲存333=9個數據。所以如果我們的B+樹一個節點可以儲存1000個鍵值,那麼3層B+樹可以儲存1000×1000×1000=10億個資料。而一般根節點是常駐記憶體的,所以一般我們查詢10億資料,只需要2次磁碟IO,真是屌炸天的設計。

  3. 因為B+樹索引的所有資料均儲存在葉子節點leaf node,而且資料是按照順序排列的。那麼B+樹使得範圍查詢,排序查詢,分組查詢以及去重查詢變得異常簡單。而B樹因為資料分散在各個節點,要實現這一點是很不容易的。 而且B+樹中各個頁之間也是通過雙向連結串列連線的,葉子節點中的資料是通過單向連結串列連線的。其實上面的B樹我們也可以對各個節點加上鍊表。其實這些不是它們之前的區別,是因為在mysql的innodb儲存引擎中,索引就是這樣儲存的。也就是說上圖中的B+樹索引就是innodb中B+樹索引真正的實現方式,準確的說應該是聚集索引(聚集索引和非聚集索引下面會講到)。

通過上圖可以看到,在innodb中,我們通過資料頁之間通過雙向連結串列連線以及葉子節點中資料之間通過單向連結串列連線的方式可以找到表中所有的資料。