想象這樣乙個場景,在設計一張使用者表時,每人的身份證號是唯一的,需要搜尋。但由於身份證號字段較大,不好將其作為主鍵。在業務**已經保證插入身份證唯一的情況下,可以選擇建立唯一索引和普通索引,這時該如何選擇呢?接下來,將從查詢和更新的執行過程進行分析。
查詢過程
假設 k 是表 t 上的索引,在搜尋select id from t where k=5時,會先從 k 這棵 b+ 的樹根開始,按層搜尋葉子節點,找到 k=5 的資料頁,然後在資料頁內容進行二分法定位。
對於普通索引,找到 k=5 的記錄後,會繼續向下查詢乙個,直到碰到第乙個不是 5 的記錄結束。
對於唯一索引,由於取值唯一,找到後直接停止。
由於 innodb 是按照資料頁為單位(資料頁預設 16 kb)進行讀寫的,在讀取一條資料時,會將整個資料頁整體讀到記憶體。 在讀入記憶體的資料頁中,如果包含 k=5 的記錄,在查詢的情況下,唯一索引比普通索引多了一次查詢和判斷的過程,可以忽略。
如果 k=5 是當前資料頁的最後一條,就需要在讀取下乙個資料頁。但這發生的概率較低,也可以忽略。
所以總得來說,普通索引和唯一索引在查詢的過程中差異不大。
change buffer
在分析唯一索引和普通索引的影響前,先來認識一下 change buffer 這個結構。
什麼是 change buffer ?
在執行更新操作時,如果要更新的資料頁在記憶體中就直接更新,否則的話,在不影響資料一致性的前提下,innodb 會將更新操作快取在 change buffer 中,從而省去了從磁碟讀取資料頁的過程。在下次查詢操作讀取到恰好需要更新的資料頁時,會將 change buffer 的更新語句執行,寫入資料頁。將操作應用到硬碟的過程叫 merge. 後台執行緒會定期 merge 或 資料庫正常關閉時,也會進行 merge 操作。
merge 的執行流程:
change buffer 實際上是可以持久化到硬碟中的資料,也就是說在記憶體和硬碟上都 change buffer 的存在。change buffer 之前叫 insert buffer,開始只對 insert buffer 有優化,後來加上了對 delete 和 update 的支援,進而改名叫 change buffer。
可以看到,先將更新操作記錄在 change buffer,減少了將磁碟資料頁讀取到記憶體的過程,語句的執行速度會有很明顯的提公升。同時,將資料讀入記憶體,會占用 buffer pool 記憶體,所以減少讀操作,還提高了記憶體使用率。
buffer pool 是記憶體中的乙個區程式設計客棧域,innodb 在訪問表和索引資料時會在其中進行快取。允許在記憶體中直接更新經常使用的資料,來加快處理速度。在一些專用的伺服器上,會將 80% 的物理記憶體分為 buffer pool.
可以通過 innodb_change_buffer_max_size 來設定 change buffer 占用 buffer pool 的大小。
change buffer 應用場景?
如上面提到,bucduqchange buffer bucduq預先儲存了更新記錄,減少了讀取資料頁的過程,從而提高效能。也就是說如果 change buffer 中針對不同的資料頁如果包含的更新記錄越多,其實收益也就越大。
因此對於寫多讀少的業務(更新完立即查詢)change buffer 發揮的作用也就越大。如常見的賬單類,日誌類等系統。
如果業務是更新完立即查詢,雖然可以將更新記錄放在 change buffer 中,但由於之後要馬上查詢資料頁,所以會立即觸發 merge 過程。這樣隨機訪問 io 次數並不會減少,反而增加了 change buffer 的維護代價,起到反效果。
更新過程
對於唯一索引來說,所有的更新操作都需要判斷是否違反唯一性約束。所以必須把所需要的資料頁讀入記憶體,然後直接更新就可以,不需要使用 change buffer. 所以 change buffer 只對普通索引有用。
具體分析下,對於一張表插入乙個新記錄:
如果新記錄要更新的資料頁在記憶體中:
對於唯一索引,找到合適的位置,判斷有沒有衝突,插入值,語句結束。
對於普通索引:找到位置,插入值,語句結束。
所以資料頁在記憶體時,唯一和普通索引就差乙個判斷的過程。可以忽略。
如果新記錄要更新的資料頁不在記憶體中:
對於唯一索引,將資料頁讀入記憶體,判斷衝突,插入,語句結束。
對於普通索引,將語句記錄在 change buffer 中,語句結束。
由於從磁碟到記憶體涉及隨機 io 訪問,是資料庫成本最高的操作之一。普通索引比唯一索引減少的讀入操作,可以有很好的效能提公升。
唯一或普通索引的選擇
通過在查詢和更新方面,兩者的比較。我們知道,在查詢過程中,除了極特殊情況,其實兩者的差異並不大。
主要的差異是在更新過程中,要更新的資料頁並不在內容中的情況。這時唯一索引,由於需要唯一性檢查,不能利用 change buffer. 多了從磁碟到內容讀取資料的過程,其中涉及隨機 io 的訪問,相對來說效率就低了。
所以如果業務需要更新不錯的效能,這時可以選用普通索引。當然一切都是建立在能保證資料準確性的前提下。
當如果更新後來緊接著查詢操作,可以考慮關掉 change buffer. 其他的情況,change buffer 都能有很好的提公升。
特別針對機械硬碟,change buffer 效果很顯著。
redo log 和 change buffer 的比較
innodb 中 redo log 的出現使其具有了 crash-safe 的能力,同時還提高了效率,通過 wal 先寫日誌,再寫磁碟。
而 change buffer 是節省了從磁碟讀入資料頁到記憶體的隨機io過程。
下面通過一條插入語句來分析下兩者間的關係:
mysql> insert into t(id,k) values(id1,k1),(id2,k2);
假設 k 為普通索引,k1 所插入的資料頁在記憶體中, k2 不在。
執行插入操作時,主要涉及了圖中這四部分的內容:
innodb buffer pool:記憶體區域
redo log:日誌
system table space(ibdata1):系統表空間
data(t.idb): 資料表空間
innodb_file_per_table 開啟時,表被建立在獨立的表空間下,否則的話被建立在系統的表空間下。
執行過程如下:
可以看到這條更新語句(包括插入,刪除,更新操作)執行成本很低,兩次寫入記憶體,1次順序寫入磁碟。虛線的操作,是後台操作,不影響響應時間。
再來看一條查詢語句:
select * from t where k in (k1, k2)
假設讀語句發生在更新語句不久,記憶體資料還在,此時讀操作就和系統表空間和 redo log 無關。
執行過程:
總結下 redo log 和 change buffer 的關係:
儲存位置:change buffer 也會持久化在硬碟裡,但儲存在系統表空間 ibdata1 裡。而 redo log 是單獨的檔案。
記錄內容:change buffer 記錄的是更新操作的內容,而 redo log 記錄的是普通資料頁的修改和 change buffer 的改動。
同步磁碟過程:同步記憶體中資料頁的修改時通過 merge 操作進行的,而不是根據 redo log.
從更新的過程來看: redo log 將隨機寫磁碟的 io 轉換成了順序寫,而 change buffer 則是節省了隨機讀磁碟的 io 消耗。
如果伺服器異常掉電,會不會導致 change buffer 丟失?
並不會,因為 change buffer 中的資料已經被記錄到 redo log 中,所以不會丟失。
由於 change buffer 一部分資料在磁碟,一部分在記憶體。對於在磁碟的資料已經 merge 所以不會丟失。
對於在記憶體中的資料:
參考資料
buffer pool
MySQL 普通索引 唯一索引和主索引
1 普通索引 普通索引 由關鍵字key或index定義的索引 的唯一任務是加快對資料的訪問速度。因此,應該只為那些最經常出現在查詢條件 wherecolumn 或排序條件 orderbycolumn 中的資料列建立索引。只要有可能,就應該選擇乙個資料最整齊 最緊湊的資料列 如乙個整數型別的資料列 來...
MySQL 普通索引 唯一索引和主索引
1.普通索引 普通索引 由關鍵字key或index定義的索引 的唯一任務是加快對資料的訪問速度。因此,應該只為那些最經常出現在查詢條件 where column 或排序條件 order by column 中的資料列建立索引。只要有可能,就應該選擇乙個資料最整齊 最緊湊的資料列 如乙個整數型別的資料...
MySQL 普通索引 唯一索引和主索引
1 普通索引 普通索引 由關鍵字key或index定義的索引 的唯一任務是加快對資料的訪問速度。因此,應該只為那些最經常出現在查詢條件 wherecolumn 或排序條件 orderbycolumn 中的資料列建立索引。只要有可能,就應該選擇乙個資料最整齊 最緊湊的資料列 如乙個整數型別的資料列 來...