總結網上已經有很多關於唯一索引和普通索引的區別,這裡就不詳細闡述了,接下來我們深入討論如何根據不同業務場景,應該選擇普通索引還是唯一索引。比如維護乙個社保管理系統,每個社保人員都有乙個唯一的身份證號,而且業務**已經保證了不會寫入兩個重複的身份證號。如果該系統需要按照身份證號查詢姓名,就會執行這樣的sql語句:
select name from suser where id_card = 『*********xx』;
所以一般會考慮在id_card 欄位上建索引。由於身份證號字段比較大,不適合用來作主鍵,索引現在有兩個選擇,要麼給id_card欄位建立唯一索引,要麼建立乙個普通索引。如果業務**已經保證了不會寫入重複的身份證號,那麼這兩個選擇邏輯上都是正確的。但是要從效能角度上來考慮,選擇的依據應該是什麼呢?下面我們就從兩種索引對查詢過程和更新過程的效能影響來分析。
我們來看一下innodb索引組織機構,假設執行
select id from t where a=3。
這個查詢語句在索引樹上查詢的過程將如下:
先是通過b+樹從樹根開始,按層序遍歷的方式搜尋到葉子節點,從而定位資料頁。
通過二分查詢來定位記錄。
唯一索引而言,查詢到滿足條件的第乙個條目(比如 (3,300))後就會停止繼續檢索。
普通索引查詢到乙個滿足條件的條目後將會繼續查詢,直到碰到第乙個不滿足a=3條件的條目。
它們的不同所帶來的效能差距卻是微乎其微的。因為innodb中是按資料頁為單位來讀寫的,也就是說,當讀取乙個條目的時候並不是將條目從磁碟讀出來,而是以頁為單位,整體讀入記憶體。既然儲存引擎是按頁讀寫的,所以說當找到a=3的條目時,它所在的資料頁已經在記憶體裡了。那麼對於普通索引需要多做的「查詢以及判斷條目是否滿足條件」的操作就只需要一次指標操作及計算。
當需要更新乙個資料頁時,如果資料頁在記憶體緩衝池(buffer pool)中就直接更新,並同時記錄redo log,但是如果這個資料頁不在記憶體中的話。在不影響一致性的前提下,innodb會將更新操作快取在寫緩衝(change buffer)中,同時記錄redo log。
那什麼是change buffer呢
它的主要目的是將對二級索引的資料操作快取下來,以此減少二級索引的隨機io,並達到操作合併的效果。
在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的操作主要有以下幾種:
如果mysql承擔大量的dml操作,則change buffer是必不可少的,他的存在就是盡量減小i/o的消耗,通過記憶體進行資料的合併操作,將多次操作操作盡量變為少量的i/o操作,從而提公升了更新操作的速度。
change buffer只限於普通索引的場景下,不適用與唯一索引。為什麼呢?
因為,假設要插入(3, 300)這個條目,首先要判斷這個條目是否在表**現過。而這必須要將資料頁讀入記憶體才能判斷。如果都已經讀入到記憶體了,那直接更新記憶體會更快,就沒必要使用 change buffer 了。
那麼innodb中插入的條目(3,300)的流程是如何的呢?
普通索引並不是所有場景使用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 所在的資料頁不在的話,如圖所示
如果a1 所在的page1 在記憶體中,則直接更新記憶體;
如果a2 所在的page2 沒有在記憶體中,則在change buffer中記錄下「要往 page2 插入一行」這個資訊
將更新page1這個動作記入到redo log 中
將change buffer記錄插入資訊這個動作記入到redo log中
第3、4寫redo log的兩次操作合在一起寫磁碟。所以從執行過程中可以發現, 執行這條更新語句的成本很低,只寫了兩處記憶體,而且還是順序寫的。圖中的兩個紅色箭頭,都是後台操作(空閒時或者必須時寫入磁碟),不影響更新的響應時間。
那麼在之後的讀請求該怎麼處理呢,比如我們要執行select * from t where a in (a1, a2);
a1 本來就在記憶體中, 之前記憶體也更新了, 所以直接從記憶體返回
讀取page2的時候,需要把page2從磁碟讀入記憶體,然後結合change buffer裡面的操作日誌生成乙個新版本並返回結果
普通索引和唯一索引在查詢能力上是沒差別的,主要考慮的是更新的影響。一般建議使用普通索引。特別是在使用機械盤的場景下,盡量把change buffer開大從而確保資料的寫入速度。
一文帶你看懂資料庫的CRUD
ddl 運算元據庫 表 1.運算元據庫 crud c create 建立 建立資料庫 create database 資料庫名稱 建立資料庫,判斷不存在,再建立 create database if not exists 資料庫名稱 建立資料庫,並指定字符集 create database 資料庫名...
小白學資料 一文看懂NoSQL資料庫
如果你關注大資料科技動向,可能聽說過乙個叫nosql資料庫的名詞,這可能讓人有些雲裡霧裡。其實我們處在乙個激動人心的技術更迭時代,以甲骨文為代表的sql資料庫已經稱霸了企業市場30年,而近年來的nosql則是強有力的更新換代的競爭者。這篇文章就通過問答的方式來給小白解釋nosql資料庫系統是什麼,無...
一文了解騰訊雲資料庫SaaS服務
本文由雲 社群發表 資料saas服務涵蓋使用者從上雲,日常運維使用,資料安全審計,及訂閱商業分析。資料傳輸服務 data transmission serivce dts 提供資料遷移 資料同步 資料訂閱於一體的資料庫資料傳輸服務,幫助您在業務不停服的前提下輕鬆完成資料庫遷移,利用實時同步通道輕鬆構...