數(shù)據(jù)庫隔離級別分為RU, RC, RR, Serialisable.以mysql 的InnoDB為例, 講解一下數(shù)據(jù)庫的隔離級別的原理是什么.
數(shù)據(jù)庫實(shí)現(xiàn)隔離級別主要還是通過兩個技術(shù)MVCC(多版本并發(fā)控制)和鎖. 不同的隔離級別, MVCC的版本生成時機(jī)不同, 鎖的范圍和釋放鎖的時機(jī)也不同,就造成了不同的隔離級別.
首先我們來了解一下什么是MVCC和鎖
基礎(chǔ)知識
幾種現(xiàn)象
我們先了解一下幾個讀現(xiàn)象: 臟讀, 不可重復(fù)讀, 幻讀
臟讀
臟讀是指一個事務(wù)A能夠讀到另一個事務(wù)B還未提交的操作.這里的問題是如果事務(wù)B發(fā)生了Rollback顯然會造成異常. 這是應(yīng)該避免的一種錯誤現(xiàn)象.
不可重復(fù)讀
不可重復(fù)讀指的是, 事務(wù)中一條數(shù)據(jù)被重復(fù)讀取的結(jié)果不一致. 假設(shè)如果有兩個事務(wù)A, B并發(fā)執(zhí)行. 事務(wù)A能夠讀取到事務(wù)B提交修改后的數(shù)據(jù).這個現(xiàn)象針對的是update, delete操作
幻讀
幻讀指的是, 兩個事務(wù)A, B并發(fā)執(zhí)行, 事務(wù)A能夠讀取到事務(wù)B中新增的數(shù)據(jù). 舉個例子, 事務(wù)A執(zhí)行select * from users where age > 0 and aget < 18;返回的結(jié)果是10條記錄. 同時事務(wù)B執(zhí)行insert into users(name, age) values('allen', 15);事務(wù)A再執(zhí)行相同的select語句將會得到11條記錄, 這就是幻讀.
MVCC
多版本并發(fā)控制, 顧名思義就是在高并發(fā)的情況下, 數(shù)據(jù)庫存在多個版本數(shù)據(jù)的情況.這個功能在mongoDB和HBase中都提供.如果沒有提供MVCC的功能, 一個事務(wù)想要讀取另一個事務(wù)正在修改的數(shù)據(jù)行的時候只能互斥等待, 這個時候并發(fā)能力就下降了, 而MVCC可以通過兩種方式去構(gòu)建出一個歷史版本的數(shù)據(jù).
- 實(shí)時保留數(shù)據(jù)的一個或多個版本
- 通過undo日志來構(gòu)建版本
數(shù)據(jù)庫的鎖
首先我們要了解一個概念, 就是數(shù)據(jù)庫的鎖并不是加在數(shù)據(jù)行上面而是加在索引行上面的.
接著我們來了解一下, 什么情況下會加鎖. 在一般修改數(shù)據(jù)記錄的情況下就會觸發(fā)加鎖的操作比如insert, delete, update, 注意我們剛剛說過了加鎖是加在索引行上面的, 如果這些操作沒有走索引是不會加行級鎖的而是加表級鎖. 而select需要顯示聲明的情況下才會觸發(fā)加鎖操作:
-
select * from users where id = 1;// 不加鎖 -
select * from users where id = 1 for update;// 加排它鎖 -
select * from users where id = 1 lock in share mode;// 加共享鎖
鎖的分類
- Share and Exclusive Locks: 共享鎖相當(dāng)于一種JAVA的讀鎖, 讀不會上鎖, 但是讀寫, 寫寫操作是互斥的.Exclusive loc則是一種排它鎖, 都是互斥的.
- Record Locks: 行級鎖, 之前說過鎖是加在索引行上的, 這個就是加在固定的索引行上加鎖.比如: select * from users where id = 1 and id = 10 for update; 就是在id = 1和id=10的索引行上面加鎖. 需要注意的是, 如果執(zhí)行select * from users where name = 'allen' for update; 如果name列上沒有加索引, 那么就會觸發(fā)表鎖, 會鎖住全表
- Gap Locks: 間隙鎖, 它會鎖住兩個索引之間的區(qū)域, 比如select * from users where id > 1 and id < 10 for update; 那么會id在(1, 10)的索引區(qū)間加上鎖.
- Next-key Locks: 也是一種間隙鎖, Gap Locks配合Record Locks形成一個閉區(qū)間的鎖, 比如select * from users where id >= 1 and id <= 10;
四種隔離機(jī)制
| 隔離級別 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
|---|---|---|---|
| Read Uncommit | yes | yes | yes |
| Read Commited | no | yes | yes |
| Read Repeatable | no | no | yes |
| Serialisable | no | no | no |
Read Uncommit
這個模式下, 數(shù)據(jù)在發(fā)生修改以后就會釋放鎖, 而不是等到事務(wù)提交以后才釋放鎖. 這就導(dǎo)致各種問題, 會出現(xiàn)臟讀, 更別提幻讀了.
Read Commited
這個時候的鎖是在事務(wù)提交以后釋放的, 我們之前說過事務(wù)中的寫操作是需要上鎖的, 所以在釋放鎖之前讀也讀不到才對呀(事務(wù)中的讀使用排它鎖),一個事務(wù)能夠讀取到另一個事務(wù)提交以后的變更, 這是為什么呢?
謎底就是因?yàn)槭褂肕VCC, MVCC的出現(xiàn)使得能夠獲取到最新版本的數(shù)據(jù)快照, 也就是說讀出來的數(shù)據(jù)實(shí)際是通過MVCC機(jī)制構(gòu)建出來的, 這里就不存在鎖的問題.那么由于使用了MVCC, 也就是說重復(fù)執(zhí)行select能夠得到最新版本的數(shù)據(jù)快照, 也就是說能夠讀取到別的事務(wù)提交以后的最新變更, 這就導(dǎo)致了不可重復(fù)讀的問題.
此外Read Commited還存在幻讀的問題, 這是因?yàn)闆]有用間隙鎖, 比如事務(wù)A中執(zhí)行 select * from users where age > 10 and age < 18 for update; 一旦事務(wù)B中insert一條age=15的數(shù)據(jù), 那么事務(wù)A可以讀到這個新增的數(shù)據(jù), 這就是幻讀.
Read Repeatable
可重復(fù)讀, 這個模式改進(jìn)了一下所以可以在不可重復(fù)讀和避免幻讀都進(jìn)行了規(guī)避.
- 通過MVCC機(jī)制從事務(wù)開始時select獲取最新版本的數(shù)據(jù), 事務(wù)進(jìn)行中的所有select都是用這個版本的數(shù)據(jù), 所以能夠保證可重復(fù)讀
- 鎖范圍調(diào)整, 在行鎖的基礎(chǔ)上添加了Gap Locks形成next-key locks, 在遍歷過的索引行范圍內(nèi)都能夠進(jìn)行上鎖, 從而阻塞其他事務(wù)往這個范圍內(nèi)insert數(shù)據(jù), 避免幻讀
Serialisable
串行化, 會自動將所有的select都轉(zhuǎn)化成select lock in share mode執(zhí)行, 也就是針對所有的讀寫操作都會加鎖, 可靠性加強(qiáng), 但是并發(fā)度大大降低.