MySQL學習筆記 普通索引和唯一索引的區別

2021-10-12 12:45:31 字數 2922 閱讀 7585

對於select id from t where k=5這樣的等值查詢語句,這個查詢語句在索引樹上查詢的過程,先是通過 b+ 樹從樹根開始,按層搜尋到葉子節點,也就是圖中右下角的這個資料頁,然後可以認為資料頁內部通過二分法來定位記。

但是普通索引和唯一索引的處理細節上略有差異:

對於普通索引來說,查詢到滿足條件的第乙個記錄 (5,500) 後,需要查詢下乙個記錄,直到碰到第乙個不滿足 k=5 條件的記錄。

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

在 innodb 中,每個資料頁的大小預設是 16kb。

當需要更新乙個資料頁時,如果資料頁在記憶體中就直接更新,而如果這個資料頁還沒有在記憶體中的話,在不影響資料一致性的前提下,innodb 會將這些更新操作快取在 change buffer 中,這樣就不需要從磁碟中讀入這個資料頁了。在下次查詢需要訪問這個資料頁的時候,將資料頁讀入記憶體,然後執行 change buffer 中與這個頁有關的操作。通過這種方式就能保證這個資料邏輯的正確性。

將 change buffer 中的操作應用到原資料頁,得到最新結果的過程稱為 merge。除了訪問這個資料頁會觸發 merge 外,系統有後台執行緒會定期 merge。在資料庫正常關閉(shutdown)的過程中,也會執行 merge 操作。

對於唯一索引來說,所有的更新操作都要先判斷這個操作是否違反唯一性約束。比如,要插入 (4,400) 這個記錄,就要先判斷現在表中是否已經存在 k=4 的記錄,而這必須要將資料頁讀入記憶體才能判斷。如果都已經讀入到記憶體了,那直接更新記憶體會更快,就沒必要使用 change buffer 了。

因此,唯一索引的更新就不能使用 change buffer,實際上也只有普通索引可以使用。

change buffer 用的是 buffer pool 裡的記憶體,因此不能無限增大。change buffer 的大小,可以通過引數innodb_change_buffer_max_size 來動態設定。這個引數設定為 50 的時候,表示 change buffer 的大小最多只能占用 buffer pool 的 50%。

第一種情況是,這個記錄要更新的目標頁在記憶體中。這時,innodb 的處理流程如下:

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

對於普通索引來說,找到 3 和 5 之間的位置,插入這個值,語句執行結束。

第二種情況是,這個記錄要更新的目標頁不在記憶體中。這時,innodb 的處理流程如下:

對於唯一索引來說,需要將資料頁讀入記憶體,判斷到沒有衝突,插入這個值,語句執行結束;

對於普通索引來說,則是將更新記錄在 change buffer,語句執行就結束了。這個普通索引的做法會重新整理我對普通索引的認知,和一般的處理思路都不一樣。

change buffer 只限於用在普通索引的場景下,而不適用於唯一索引。

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

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

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

mysql> insert into t(id,k) values(id1,k1),(id2,k2);

假設當前 k 索引樹的狀態,查詢到位置後,k1 所在的資料頁在記憶體 (innodb buffer pool) 中,k2 所在的資料頁不在記憶體中。

這條更新語句做了如下的操作(按照圖中的數字順序):

page 1 在記憶體中,直接更新記憶體;

page 2 沒有在記憶體中,就在記憶體的 change buffer 區域,記錄下「我要往 page 2 插入一行」這個資訊

將上述兩個動作記入 redo log 中。

那在這之後的讀請求,要怎麼處理呢?

比如,我們現在要執行 select * from t where k in (k1, k2)。如果讀語句發生在更新語句後不久,記憶體中的資料都還在,那麼此時的這兩個讀操作就與系統表空間(ibdata1)和 redo log(ib_log_filex)無關了。

從圖中可以看到:

讀 page 1 的時候,直接從記憶體返回。

讀 page 2 的時候,需要把 page 2 從磁碟讀入記憶體中,然後應用 change buffer 裡面的操作日誌,生成乙個正確的版本並返回結果。可以看到,直到需要讀 page 2 的時候,這個資料頁才會被讀入記憶體。

change buffer merge 的執行流程是這樣的:

從磁碟讀入資料頁到記憶體(老版本的資料頁);

從 change buffer 裡找出這個資料頁的 change buffer 記錄 (可能有多個),依次應用,得到新版資料頁;

寫 redo log。

這個 redo log 包含了資料的變更和 change buffer 的變更。到這裡 merge 過程就結束了。

這時候,資料頁和記憶體中 change buffer 對應的磁碟位置都還沒有修改,屬於髒頁,之後各自刷回自己的物理資料,就是另外乙個過程了。

後記:唯一索引的處理機制和其他資料庫基本上一致,都是先從磁碟上讀取資料頁,然後進行插入或更新資料頁對應的記憶體頁,並修改索引樹;

普通索引不需要從磁碟上進行隨機io讀取資料頁,而是直接寫入change buffer,減少了隨機io的消耗,其實是滯後 了隨機io的消耗,提高了效能。

MySQL 普通索引 唯一索引和主索引

1 普通索引 普通索引 由關鍵字key或index定義的索引 的唯一任務是加快對資料的訪問速度。因此,應該只為那些最經常出現在查詢條件 wherecolumn 或排序條件 orderbycolumn 中的資料列建立索引。只要有可能,就應該選擇乙個資料最整齊 最緊湊的資料列 如乙個整數型別的資料列 來...

MySQL 普通索引 唯一索引和主索引

1.普通索引 普通索引 由關鍵字key或index定義的索引 的唯一任務是加快對資料的訪問速度。因此,應該只為那些最經常出現在查詢條件 where column 或排序條件 order by column 中的資料列建立索引。只要有可能,就應該選擇乙個資料最整齊 最緊湊的資料列 如乙個整數型別的資料...

MySQL 普通索引 唯一索引和主索引

1 普通索引 普通索引 由關鍵字key或index定義的索引 的唯一任務是加快對資料的訪問速度。因此,應該只為那些最經常出現在查詢條件 wherecolumn 或排序條件 orderbycolumn 中的資料列建立索引。只要有可能,就應該選擇乙個資料最整齊 最緊湊的資料列 如乙個整數型別的資料列 來...