3.普通索引和唯一索引,應該怎麼選擇

【版權申明】未經博主同意,謝絕轉載!(請尊重原創,博主保留追究權)
https://mp.csdn.net/mdeditor/91355389
出自【zzf__的博客】

1.場景:

按照身份證號查姓名
由於身份證號字段比較大,我不建議你把身份證號當做主鍵
那麼現在你有兩個選擇,要麼給 id_card 字段創建唯一索引,要麼創建一個普通索引。如果業務代碼已經保證了不會寫入重複的身份證號,那麼這兩個選擇邏輯上都是正確的。

2.查詢過程性能分析

先找到數據頁
假設,執行查詢的語句是 select id from T where k=5。這個查詢語句在索引樹上查找的過程,先是通過 B+ 樹從樹根開始,按層搜索到葉子節點,也就是圖中右下角的這個數據頁,然後可以認爲數據頁內部通過二分法來定位記錄。

1.普通索引查詢

查找到滿足條件的第一個記錄 (5,500) 後,需要查找下一個記錄,直到碰到第一個不滿足 k=5 條件的記錄。

2.唯一索引查詢

由於索引定義了唯一性,查找到第一個滿足條件的記錄後,就會停止繼續檢索。

3.InnoDB讀取數據的方式

InnoDB 的數據是按數據頁爲單位來讀寫的
也就是說,當需要讀一條記錄的時候,並不是將這個記錄本身從磁盤讀出來,而是以頁爲單位,將其整體讀入內存。在 InnoDB 中,每個數據頁的大小默認是 16KB。

4. 性能對比

因爲引擎是按頁讀寫的,所以說,當找到 k=5 的記錄的時候,它所在的數據頁就都在內存裏了。那麼,對於普通索引來說,要多做的那一次“查找和判斷下一條記錄”的操作,就只需要一次指針尋找和一次計算。

當然,如果 k=5 這個記錄剛好是這個數據頁的最後一個記錄,那麼要取下一個記錄,必須讀取下一個數據頁,這個操作會稍微複雜一些。

但是,我們之前計算過,對於整型字段,一個數據頁可以放近千個 key,因此出現這種情況的概率會很低。所以,我們計算平均性能差異時,仍可以認爲這個操作成本對於現在的 CPU 來說可以忽略不計。

綜上:普通索引和唯一索引在查詢的性能可以說差不多

4. change buffer

4.1 change buffer簡介:

當需要更新一個數據頁時,如果數據頁在內存中就直接更新,而如果這個數據頁還沒有在內存中的話,在不影響數據一致性的前提下,InooDB 會將這些更新操作緩存在 change buffer 中,這樣就不需要從磁盤中讀入這個數據頁了。

在下次查詢需要訪問這個數據頁的時候,將數據頁讀入內存,然後執行 change buffer 中與這個頁有關的操作。通過這種方式就能保證這個數據邏輯的正確性。

需要說明的是,雖然名字叫作 change buffer,實際上它是可以持久化的數據。也就是說,change buffer 在內存中有拷貝,也會被寫入到磁盤上。

4.2 change buffer的內存介紹:

change buffer 用的是 buffer pool 裏的內存,因此不能無限增大。change buffer 的大小,可以通過參數 innodb_change_buffer_max_size 來動態設置。這個參數設置爲 50 的時候,表示 change buffer 的大小最多隻能佔用 buffer pool 的 50%。

4.3 什麼是Merge:

將 change buffer 中的操作應用到原數據頁,得到最新結果的過程稱爲 merge。

4.4 Merge時機:

除了訪問這個數據頁會觸發 merge 外,系統有後臺線程會定期 merge。在數據庫正常關閉(shutdown)的過程中,也會執行 merge 操作。

4.5 Change buffer使用場景

1.change buffer 對更新過程的加速作用,
2.對於唯一索引來說,所有的更新操作都要先判斷這個操作是否違反唯一性約束。
因此,唯一索引的更新就不能使用 change buffer,實際上也只有普通索引可以使用。

普通索引的所有場景,使用 change buffer 都可以起到加速作用嗎?

因爲 merge 的時候是真正進行數據更新的時刻,而 change buffer 的主要目的就是將記錄的變更動作緩存下來,所以在一個數據頁做 merge 之前,change buffer 記錄的變更越多(也就是這個頁面上要更新的次數越多),收益就越大。

寫入之後不馬上被訪問到&寫多讀少效果最好:
因此,對於寫多讀少的業務來說,頁面在寫完以後馬上被訪問到的概率比較小,此時 change buffer 的使用效果最好。這種業務模型常見的就是賬單類、日誌類的系統。

寫入之後馬上會做查詢副作用:
反過來,假設一個業務的更新模式是寫入之後馬上會做查詢,那麼即使滿足了條件,將更新先記錄在 change buffer,但之後由於馬上要訪問這個數據頁,會立即觸發 merge 過程。這樣隨機訪問 IO 的次數不會減少,反而增加了 change buffer 的維護代價。所以,對於這種業務模式來說,change buffer 反而起到了副作用。

4.6 Change buffer帶來的好處:

1.顯然,如果能夠將更新操作先記錄在 change buffer,減少讀磁盤,語句的執行速度會得到明顯的提升。
2.而且,數據讀入內存是需要佔用 buffer pool 的,所以這種方式還能夠避免佔用內存,提高內存利用率。

5. 更新過程性能分析

插入一個新記錄 (4,400)

情況一:這個記錄要更新的目標頁在內存中

唯一索引:找到 3 和 5 之間的位置,判斷到沒有衝突,插入這個值,語句執行結束;
普通索引:找到 3 和 5 之間的位置,插入這個值,語句執行結束。

這樣看來,普通索引和唯一索引對更新語句性能影響的差別,只是一個判斷,只會耗費微小的 CPU 時間。

情況二:這個記錄要更新的目標頁不在內存中

對於唯一索引來說,需要將數據頁讀入內存,判斷到沒有衝突,插入這個值,語句執行結束;
對於普通索引來說,則是將更新記錄在 change buffer,語句執行就結束了。
將數據從磁盤讀入內存涉及隨機 IO 的訪問,是數據庫裏面成本最高的操作之一。change buffer 因爲減少了隨機磁盤訪問,所以對更新性能的提升是會很明顯的。

之前我就碰到過一件事兒,有個 DBA 的同學跟我反饋說,他負責的某個業務的庫內存命中率突然從 99% 降低到了 75%,整個系統處於阻塞狀態,更新語句全部堵住。而探究其原因後,我發現這個業務有大量插入數據的操作,而他在前一天把其中的某個普通索引改成了唯一索引。

性能對比

綜上:普通索引優勢大

6. 索引選擇和實踐

普通索引和唯一索引在查詢能力上是沒差別的,主要考慮的是對更新性能的影響。所以,我建議你儘量選擇普通索引。

如果所有的更新後面,都馬上伴隨着對這個記錄的查詢,那麼你應該關閉change buffer。而在其他情況下,change buffer 都能提升更新性能。

在實際使用中,你會發現,普通索引和 change buffer 的配合使用,對於數據量大的表的更新優化還是很明顯的。

特別地,在使用機械硬盤時,change buffer 這個機制的收效是非常顯著的。所以,當你有一個類似“歷史數據”的庫,並且出於成本考慮用的是機械硬盤時,那你應該特別關注這些表裏的索引,儘量使用普通索引,然後把 change buffer 儘量開大,以確保這個“歷史數據”表的數據寫入速度。

7. Change buffer 和 redo log

8. 補充

評論區大家對“是否使用唯一索引”有比較多的討論,主要是糾結在“業務可能無法確保”的情況。這裏,我再說明一下:

首先,業務正確性優先。咱們這篇文章的前提是“業務代碼已經保證不會寫入重複數據”的情況下,討論性能問題。如果業務不能保證,或者業務就是要求數據庫來做約束,那麼沒得選,必須創建唯一索引。這種情況下,本篇文章的意義在於,如果碰上了大量插入數據慢、內存命中率低的時候,可以給你多提供一個排查思路。

然後,在一些“歸檔庫”的場景,你是可以考慮使用普通索引的。比如,線上數據只需要保留半年,然後歷史數據保存在歸檔庫。這時候,歸檔數據已經是確保沒有唯一鍵衝突了。要提高歸檔效率,可以考慮把表裏面的唯一索引改成普通索引。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章