如何選擇普通索引和唯一索引?|CSDN博文精選

如何選擇普通索引和唯一索引?|CSDN博文精選

作者 | .NY&XX

出品 | CSDN博客

網上已經有很多關於唯一索引和普通索引的區別,這裡就不詳細闡述了,接下來我們深入討論如何根據不同業務場景,應該選擇普通索引還是唯一索引。比如維護一個社保管理系統,每個社保人員都有一個唯一的身份證號,而且業務代碼已經保證了不會寫入兩個重複的身份證號。如果該系統需要按照身份證號查詢姓名,就會執行這樣的SQL語句:

<code>select name from suser where id_card = ‘xxxxxxxxxxx’;
/<code>

所以一般會考慮在id_card 字段上建索引。由於身份證號字段比較大,不適合用來作主鍵,索引現在有兩個選擇,要麼給id_card字段創建唯一索引,要麼創建一個普通索引。如果業務代碼已經保證了不會寫入重複的身份證號,那麼這兩個選擇邏輯上都是正確的。但是要從性能角度上來考慮,選擇的依據應該是什麼呢?下面我們就從兩種索引對查詢過程和更新過程的性能影響來分析。

如何选择普通索引和唯一索引?|CSDN博文精选

查詢操作

我們來看一下InnoDB索引組織機構,假設執行:

<code>select id from t where a=3/<code>

這個查詢語句在索引樹上查找的過程將如下:

  • 先是通過B+樹從樹根開始,按層序遍歷的方式搜索到葉子節點,從而定位數據頁。

  • 通過二分查找來定位記錄。

唯一索引而言,查找到滿足條件的第一個條目(比如 (3,300))後就會停止繼續檢索。普通索引查找到一個滿足條件的條目後將會繼續查找,直到碰到第一個不滿足a=3條件的條目。

它們的不同所帶來的性能差距卻是微乎其微的。因為InnoDB中是按數據頁為單位來讀寫的,也就是說,當讀取一個條目的時候並不是將條目從磁盤讀出來,而是以頁為單位,整體讀入內存。既然存儲引擎是按頁讀寫的,所以說當找到a=3的條目時,它所在的數據頁已經在內存裡了。那麼對於普通索引需要多做的“查找以及判斷條目是否滿足條件”的操作就只需要一次指針操作及計算。

如何选择普通索引和唯一索引?|CSDN博文精选

更新操作

當需要更新一個數據頁時,如果數據頁在內存緩衝池(buffer pool)中就直接更新,並同時記錄redo log,但是如果這個數據頁不在內存中的話。在不影響一致性的前提下,InnoDB會將更新操作緩存在寫緩衝(change buffer)中,同時記錄redo log。

如何选择普通索引和唯一索引?|CSDN博文精选

寫緩衝(change buffer)

那什麼是change buffer呢?

它的主要目的是將對二級索引的數據操作緩存下來,以此減少二級索引的隨機IO,並達到操作合併的效果。

如何选择普通索引和唯一索引?|CSDN博文精选

在MySQL5.5之前的版本中,由於只支持緩存insert操作,所以最初叫做insert buffer,只是後來的版本中支持了更多的操作類型(操作類型包括insert、update、delete)緩存,才改叫change buffer。

change buffer的數據結構上是一顆b+樹,存儲在ibdata系統表空間中,根頁為ibdata的第4個page(FSP_IBUF_TREE_ROOT_PAGE_NO)。

將change buffer中的操作應用到原數據頁從而得到最新結果的過程被稱為merge。merge 的時候才是是真正進行數據更新的時刻,change buffer 將條目的變更動作進行緩存。在一個數據頁做 merge 之前,change buffer 記錄的變更越多(也就是這個頁面上要更新的次數越多),收益就越大。

一般來說,觸發merge的操作主要有以下幾種:

  • 訪問這個數據頁;

  • master thread線程每秒或每10秒進行一次merge insert buffer的操作;

  • 在數據庫正常關閉的時候。

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

如何选择普通索引和唯一索引?|CSDN博文精选

change buffer狀態查看

如何选择普通索引和唯一索引?|CSDN博文精选
  • seg size 為插入緩衝區的總大小(頁的數量X16KB);

  • merges表示已經合併的merge的數量;

  • merged operations: insert 插入記錄被merge的次數;

  • delete mark 刪除操作被merge的次數;

  • delete 更新操作被merge了多少次。

如何选择普通索引和唯一索引?|CSDN博文精选

change buffer佔用buffer pool

數據讀入內存是需要佔用buffer pool的,採用這種方式能夠避免佔用內存,提升內存利用率。

change buffer用的是buffer pool的內存,因此不能無線增大,它通過參數innodb_change_buffer_max_size來設置,這個參數表示佔用內存的比例,默認是25%,最大值為50%,一般在寫多讀少的場景下才需要設置。

如何选择普通索引和唯一索引?|CSDN博文精选如何选择普通索引和唯一索引?|CSDN博文精选

change buffer帶來什麼好處?

如果MySQL承擔大量的DML操作,則change buffer是必不可少的,他的存在就是儘量減小I/O的消耗,通過內存進行數據的合併操作,將多次操作操作儘量變為少量的I/O操作,從而提升了更新操作的速度。

如何选择普通索引和唯一索引?|CSDN博文精选

什麼場景適合開啟change buffer?

change buffer只限於普通索引的場景下,不適用與唯一索引。為什麼呢?

因為,假設要

插入(3, 300)這個條目,首先要判斷這個條目是否在表中出現過。而這必須要將數據頁讀入內存才能判斷。如果都已經讀入到內存了,那直接更新內存會更快,就沒必要使用 change buffer 了。

那麼InnoDB中插入的條目(3,300)的流程是如何的呢?

如果這個條目要更新的數據頁在內存中:

  • 對於唯一索引,找到2和4的位置,判斷沒有衝突後,插入這個值,執行結束

  • 對於普通索引,找到2和4的位置,插入這個值,執行結束

如果這個條目要更新的數據頁不在內存中:

  • 對於唯一索引,需要將數據頁讀入內存,然後判斷有沒有衝突,然後進行插入

  • 對於普通索引,只需要將條目更新操作記錄在change buffer就執行結束了。

如何选择普通索引和唯一索引?|CSDN博文精选

不是所有場景都可以用change buffer

普通索引並不是所有場景使用change buffer都能受益,對於寫多讀少的業務來說,頁面在寫完以後馬上被訪問到的概率比較小,此時 change buffer 的使用效果最好。

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

舉個例子:

假設要執行insert into t values(id1,a1),(id2,a2);

假設a1 所在的數據頁在內存 (InnoDB buffer pool) 中,a2 所在的數據頁不在的話,如圖所示:

如何选择普通索引和唯一索引?|CSDN博文精选
  1. 如果a1 所在的Page1 在內存中,則直接更新內存;

  2. 如果a2 所在的Page2 沒有在內存中,則在change buffer中記錄下“要往 Page2 插入一行”這個信息;

  3. 將更新Page1這個動作記入到redo log 中;

  4. 將change buffer記錄插入信息這個動作記入到redo log中。

第3、4寫redo log的兩次操作合在一起寫磁盤。所以從執行過程中可以發現, 執行這條更新語句的成本很低,只寫了兩處內存,而且還是順序寫的。圖中的兩個紅色箭頭,都是後臺操作(空閒時或者必須時寫入磁盤),不影響更新的響應時間。

那麼在之後的讀請求該怎麼處理呢,比如我們要執行select * from t where a in (a1, a2);

如何选择普通索引和唯一索引?|CSDN博文精选

a1 本來就在內存中, 之前內存也更新了, 所以直接從內存返回。

讀取Page2的時候,需要把Page2從磁盤讀入內存,然後結合change buffer裡面的操作日誌生成一個新版本並返回結果。

如何选择普通索引和唯一索引?|CSDN博文精选

總結

普通索引和唯一索引在查詢能力上是沒差別的,主要考慮的是更新的影響。一般建議使用普通索引。特別是在使用機械盤的場景下,儘量把change buffer開大從而確保數據的寫入速度。

聲明:本文為CSDN博主「.NY&XX」的原創文章,原文鏈接:https://blog.csdn.net/songguangfan/article/details/103059623。


分享到:


相關文章: