MySQL唯一索引和普通索引選哪個(gè)?
想象這樣一個(gè)場(chǎng)景,在設(shè)計(jì)一張用戶(hù)表時(shí),每人的身份證號(hào)是唯一的,需要搜索。但由于身份證號(hào)字段較大,不好將其作為主鍵。在業(yè)務(wù)代碼已經(jīng)保證插入身份證唯一的情況下,可以選擇建立唯一索引和普通索引,這時(shí)該如何選擇呢?接下來(lái),將從查詢(xún)和更新的執(zhí)行過(guò)程進(jìn)行分析。
查詢(xún)過(guò)程
假設(shè) k 是表 t 上的索引,在搜索 select id from t where k=5
時(shí),會(huì)先從 k 這棵 B+ 的樹(shù)根開(kāi)始,按層搜索葉子節(jié)點(diǎn),找到 k=5 的數(shù)據(jù)頁(yè),然后在數(shù)據(jù)頁(yè)內(nèi)容進(jìn)行二分法定位。
對(duì)于普通索引,找到 k=5 的記錄后,會(huì)繼續(xù)向下查找一個(gè),直到碰到第一個(gè)不是 5 的記錄結(jié)束。
對(duì)于唯一索引,由于取值唯一,找到后直接停止。
由于 InnoDB 是按照數(shù)據(jù)頁(yè)為單位(數(shù)據(jù)頁(yè)默認(rèn) 16 KB)進(jìn)行讀寫(xiě)的,在讀取一條數(shù)據(jù)時(shí),會(huì)將整個(gè)數(shù)據(jù)頁(yè)整體讀到內(nèi)存。 在讀入內(nèi)存的數(shù)據(jù)頁(yè)中,如果包含 k=5 的記錄,在查詢(xún)的情況下,唯一索引比普通索引多了一次查找和判斷的過(guò)程,可以忽略。
如果 k=5 是當(dāng)前數(shù)據(jù)頁(yè)的最后一條,就需要在讀取下一個(gè)數(shù)據(jù)頁(yè)。但這發(fā)生的概率較低,也可以忽略。
所以總得來(lái)說(shuō),普通索引和唯一索引在查詢(xún)的過(guò)程中差異不大。
change buffer
在分析唯一索引和普通索引的影響前,先來(lái)認(rèn)識(shí)一下 change buffer 這個(gè)結(jié)構(gòu)。
什么是 change buffer ?
在執(zhí)行更新操作時(shí),如果要更新的數(shù)據(jù)頁(yè)在內(nèi)存中就直接更新,否則的話,在不影響數(shù)據(jù)一致性的前提下,InnoDB 會(huì)將更新操作緩存在 change buffer 中,從而省去了從磁盤(pán)讀取數(shù)據(jù)頁(yè)的過(guò)程。在下次查詢(xún)操作讀取到恰好需要更新的數(shù)據(jù)頁(yè)時(shí),會(huì)將 change buffer 的更新語(yǔ)句執(zhí)行,寫(xiě)入數(shù)據(jù)頁(yè)。將操作應(yīng)用到硬盤(pán)的過(guò)程叫 merge. 后臺(tái)線程會(huì)定期 merge 或 數(shù)據(jù)庫(kù)正常關(guān)閉時(shí),也會(huì)進(jìn)行 merge 操作。
merge 的執(zhí)行流程:
- 從磁盤(pán)讀入老版本數(shù)據(jù)頁(yè)。
- 從 change buffer中找出和該數(shù)據(jù)頁(yè)關(guān)聯(lián)的記錄,依次應(yīng)用,得到新版數(shù)據(jù)頁(yè)。
- 寫(xiě) redo log,記錄數(shù)據(jù)的變更和 change buffer 的變更。
change buffer 實(shí)際上是可以持久化到硬盤(pán)中的數(shù)據(jù),也就是說(shuō)在內(nèi)存和硬盤(pán)上都 change buffer 的存在。change buffer 之前叫 insert buffer,開(kāi)始只對(duì) insert buffer 有優(yōu)化,后來(lái)加上了對(duì) delete 和 update 的支持,進(jìn)而改名叫 change buffer。
可以看到,先將更新操作記錄在 change buffer,減少了將磁盤(pán)數(shù)據(jù)頁(yè)讀取到內(nèi)存的過(guò)程,語(yǔ)句的執(zhí)行速度會(huì)有很明顯的提升。同時(shí),將數(shù)據(jù)讀入內(nèi)存,會(huì)占用 buffer pool 內(nèi)存,所以減少讀操作,還提高了內(nèi)存使用率。
Buffer Pool 是內(nèi)存中的一個(gè)區(qū)域,InnoDB 在訪問(wèn)表和索引數(shù)據(jù)時(shí)會(huì)在其中進(jìn)行緩存。允許在內(nèi)存中直接更新經(jīng)常使用的數(shù)據(jù),來(lái)加快處理速度。在一些專(zhuān)用的服務(wù)器上,會(huì)將 80% 的物理內(nèi)存分為 buffer pool.
可以通過(guò) innodb_change_buffer_max_size 來(lái)設(shè)置 change buffer 占用 buffer pool 的大小。
change buffer 應(yīng)用場(chǎng)景?
如上面提到,change buffer 預(yù)先保存了更新記錄,減少了讀取數(shù)據(jù)頁(yè)的過(guò)程,從而提高性能。也就是說(shuō)如果 change buffer 中針對(duì)不同的數(shù)據(jù)頁(yè)如果包含的更新記錄越多,其實(shí)收益也就越大。
因此對(duì)于寫(xiě)多讀少的業(yè)務(wù)(更新完立即查詢(xún))change buffer 發(fā)揮的作用也就越大。如常見(jiàn)的賬單類(lèi),日志類(lèi)等系統(tǒng)。
如果業(yè)務(wù)是更新完立即查詢(xún),雖然可以將更新記錄放在 change buffer 中,但由于之后要馬上查詢(xún)數(shù)據(jù)頁(yè),所以會(huì)立即觸發(fā) merge 過(guò)程。這樣隨機(jī)訪問(wèn) IO 次數(shù)并不會(huì)減少,反而增加了 change buffer 的維護(hù)代價(jià),起到反效果。
更新過(guò)程
對(duì)于唯一索引來(lái)說(shuō),所有的更新操作都需要判斷是否違反唯一性約束。所以必須把所需要的數(shù)據(jù)頁(yè)讀入內(nèi)存,然后直接更新就可以,不需要使用 change buffer. 所以 change buffer 只對(duì)普通索引有用。
具體分析下,對(duì)于一張表插入一個(gè)新記錄:
如果新記錄要更新的數(shù)據(jù)頁(yè)在內(nèi)存中:
對(duì)于唯一索引,找到合適的位置,判斷有沒(méi)有沖突,插入值,語(yǔ)句結(jié)束。
對(duì)于普通索引:找到位置,插入值,語(yǔ)句結(jié)束。
所以數(shù)據(jù)頁(yè)在內(nèi)存時(shí),唯一和普通索引就差一個(gè)判斷的過(guò)程。可以忽略。
如果新記錄要更新的數(shù)據(jù)頁(yè)不在內(nèi)存中:
對(duì)于唯一索引,將數(shù)據(jù)頁(yè)讀入內(nèi)存,判斷沖突,插入,語(yǔ)句結(jié)束。
對(duì)于普通索引,將語(yǔ)句記錄在 change buffer 中,語(yǔ)句結(jié)束。
由于從磁盤(pán)到內(nèi)存涉及隨機(jī) IO 訪問(wèn),是數(shù)據(jù)庫(kù)成本最高的操作之一。普通索引比唯一索引減少的讀入操作,可以有很好的性能提升。
唯一或普通索引的選擇
通過(guò)在查詢(xún)和更新方面,兩者的比較。我們知道,在查詢(xún)過(guò)程中,除了極特殊情況,其實(shí)兩者的差異并不大。
主要的差異是在更新過(guò)程中,要更新的數(shù)據(jù)頁(yè)并不在內(nèi)容中的情況。這時(shí)唯一索引,由于需要唯一性檢查,不能利用 change buffer. 多了從磁盤(pán)到內(nèi)容讀取數(shù)據(jù)的過(guò)程,其中涉及隨機(jī) IO 的訪問(wèn),相對(duì)來(lái)說(shuō)效率就低了。
所以如果業(yè)務(wù)需要更新不錯(cuò)的性能,這時(shí)可以選用普通索引。當(dāng)然一切都是建立在能保證數(shù)據(jù)準(zhǔn)確性的前提下。
當(dāng)如果更新后來(lái)緊接著查詢(xún)操作,可以考慮關(guān)掉 change buffer. 其他的情況,change buffer 都能有很好的提升。
特別針對(duì)機(jī)械硬盤(pán),change buffer 效果很顯著。
redo log 和 change buffer 的比較
InnoDB 中 redo log 的出現(xiàn)使其具有了 crash-safe 的能力,同時(shí)還提高了效率,通過(guò) WAL 先寫(xiě)日志,再寫(xiě)磁盤(pán)。
而 change buffer 是節(jié)省了從磁盤(pán)讀入數(shù)據(jù)頁(yè)到內(nèi)存的隨機(jī)IO過(guò)程。
下面通過(guò)一條插入語(yǔ)句來(lái)分析下兩者間的關(guān)系:
mysql> insert into t(id,k) values(id1,k1),(id2,k2);
假設(shè) k 為普通索引,k1 所插入的數(shù)據(jù)頁(yè)在內(nèi)存中, k2 不在。
執(zhí)行插入操作時(shí),主要涉及了圖中這四部分的內(nèi)容:
InnoDB buffer pool:內(nèi)存區(qū)域
redo log:日志
system table space(ibdata1):系統(tǒng)表空間
data(t.idb): 數(shù)據(jù)表空間
innodb_file_per_table 開(kāi)啟時(shí),表被創(chuàng)建在獨(dú)立的表空間下,否則的話被創(chuàng)建在系統(tǒng)的表空間下。
執(zhí)行過(guò)程如下:
- k1 所在的 page1 在內(nèi)存中,直接更新內(nèi)存
- k2 所在的 page2 不在內(nèi)存中,記錄在 change buffer.
- 將 k1 和 k2 的操作記錄在 redo log.
- 提交事務(wù)。
可以看到這條更新語(yǔ)句(包括插入,刪除,更新操作)執(zhí)行成本很低,兩次寫(xiě)入內(nèi)存,1次順序?qū)懭氪疟P(pán)。虛線的操作,是后臺(tái)操作,不影響響應(yīng)時(shí)間。
再來(lái)看一條查詢(xún)語(yǔ)句:
select * from t where k in (k1, k2)
假設(shè)讀語(yǔ)句發(fā)生在更新語(yǔ)句不久,內(nèi)存數(shù)據(jù)還在,此時(shí)讀操作就和系統(tǒng)表空間和 redo log 無(wú)關(guān)。
執(zhí)行過(guò)程:
- 讀取 k1 所在的 page1,在內(nèi)存中,直接返回。注意,并沒(méi)有讀磁盤(pán)上的數(shù)據(jù),而且磁盤(pán)上的數(shù)據(jù)還有可能是之前的版本的。
- 讀取 k2 所在的 page2,這時(shí)需要將 page2 從磁盤(pán)加載到內(nèi)存,并應(yīng)用 change buffer 的內(nèi)容,然后返回正確的結(jié)果。從這里也能看出,change buffer 不適用于更新完立馬去讀的情況。
總結(jié)下 redo log 和 change buffer 的關(guān)系:
存儲(chǔ)位置:change buffer 也會(huì)持久化在硬盤(pán)里,但保存在系統(tǒng)表空間 ibdata1 里。而 redo log 是單獨(dú)的文件。
記錄內(nèi)容:change buffer 記錄的是更新操作的內(nèi)容,而 redo log 記錄的是普通數(shù)據(jù)頁(yè)的修改和 change buffer 的改動(dòng)。
同步磁盤(pán)過(guò)程:同步內(nèi)存中數(shù)據(jù)頁(yè)的修改時(shí)通過(guò) merge 操作進(jìn)行的,而不是根據(jù) redo log.
從更新的過(guò)程來(lái)看: redo log 將隨機(jī)寫(xiě)磁盤(pán)的 IO 轉(zhuǎn)換成了順序?qū)?,?change buffer 則是節(jié)省了隨機(jī)讀磁盤(pán)的 IO 消耗。
如果服務(wù)器異常掉電,會(huì)不會(huì)導(dǎo)致 change buffer 丟失?
并不會(huì),因?yàn)?change buffer 中的數(shù)據(jù)已經(jīng)被記錄到 redo log 中,所以不會(huì)丟失。
由于 change buffer 一部分?jǐn)?shù)據(jù)在磁盤(pán),一部分在內(nèi)存。對(duì)于在磁盤(pán)的數(shù)據(jù)已經(jīng) merge 所以不會(huì)丟失。
對(duì)于在內(nèi)存中的數(shù)據(jù):
- 如果 change buffer 寫(xiě)入,但 redo log 未提交,binlog 未提交,事務(wù)會(huì)回滾,這部分?jǐn)?shù)據(jù)不存在。
- 如果 change buffer 寫(xiě)入,redo log 寫(xiě)入,binlog 寫(xiě)入,并已提交,不會(huì)丟失。從 redo log 直接恢復(fù)。
- 如果 change buffer 寫(xiě)入,redo log 寫(xiě)入但未 commit,binlog 寫(xiě)入,從 binlog 恢復(fù) redo log 再恢復(fù) change buffer.
參考資料
以上就是MySQL唯一索引和普通索引選哪個(gè)?的詳細(xì)內(nèi)容,更多關(guān)于MySQL唯一索引和普通索引的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
總結(jié)MySQL建表、查詢(xún)優(yōu)化的一些實(shí)用小技巧
本篇文章是對(duì)MySQL建表以及查詢(xún)優(yōu)化的一些實(shí)用小技巧進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-07-07mysql 5.7.21 winx64綠色版安裝配置方法圖文教程
這篇文章主要為大家詳細(xì)介紹了mysql 5.7.21 winx64綠色版安裝配置方法圖文教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-09-09MySQL數(shù)據(jù)庫(kù)備份以及常用備份工具集合
數(shù)據(jù)庫(kù)備份種類(lèi)按照數(shù)據(jù)庫(kù)大小備份,有四種類(lèi)型,分別應(yīng)用于不同場(chǎng)合。本文將MySQL 數(shù)據(jù)庫(kù)備份種類(lèi)以及常用備份工具進(jìn)行匯總,方便大家學(xué)習(xí)。2015-08-08MYSQL中 TYPE=MyISAM 錯(cuò)誤的解決方法
這篇文章主要介紹了MYSQL中 TYPE=MyISAM 錯(cuò)誤的解決方法,需要的朋友可以參考下2014-08-08mysql數(shù)據(jù)庫(kù)修改數(shù)據(jù)表引擎的方法
對(duì)于MySQL數(shù)據(jù)庫(kù),如果你要使用事務(wù)以及行級(jí)鎖就必須使用INNODB引擎。如果你要使用全文索引,那必須使用myisam,那如何修改修改MySQL的引擎為INNODB呢,下面介紹一個(gè)修改方法2014-01-01Mysql動(dòng)態(tài)更新數(shù)據(jù)庫(kù)腳本的示例講解
今天小編就為大家分享一篇關(guān)于Mysql動(dòng)態(tài)更新數(shù)據(jù)庫(kù)腳本的示例講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12