事務(wù)特性定義acid
- 原子性(A):事務(wù)是最小單位,不可再分
- 一致性?:事務(wù)要求所有的DML語句操作的時(shí)候,必須保證同時(shí)成功或者同時(shí)失敗
- 隔離性(I):事務(wù)A和事務(wù)B之間具有隔離性
- 持久性(D):是事務(wù)的保證,事務(wù)終結(jié)的標(biāo)志(內(nèi)存的數(shù)據(jù)持久到硬盤文件中)
隔離級別
- 讀未提交:read uncommitted
- 讀已提交:read committed
- 可重復(fù)讀:repeatable read
- 串行化:serializable
出現(xiàn)問題
- 臟讀(Drity Read):某個(gè)事務(wù)已更新一份數(shù)據(jù),另一個(gè)事務(wù)在此時(shí)讀取了同一份數(shù)據(jù),由于某些原因,前一個(gè)RollBack了操作,則后一個(gè)事務(wù)所讀取的數(shù)據(jù)就會是不正確的。
- 不可重復(fù)讀(Non-repeatable read):在一個(gè)事務(wù)的兩次查詢之中數(shù)據(jù)不一致,這可能是兩次查詢過程中間插入了一個(gè)事務(wù)更新的原有的數(shù)據(jù)。強(qiáng)調(diào)是讀讀場景
- 幻讀(Phantom Read):強(qiáng)調(diào)的是讀寫場景,在事物a中查詢無記錄后準(zhǔn)備插入,在插入前b事務(wù)先插入并提交,導(dǎo)致a事務(wù)插入失敗,此場景為幻讀。
解決問題
![]()
其中mysl innodb引擎場景下通過next-key lock(行鎖+gap鎖)解決了rr級別的當(dāng)前讀的幻讀問題,通過mvcc機(jī)制解決了rr級別的快照讀的幻讀問題
mvcc
是一個(gè)通用的解決方案的概念,目的是解決并發(fā)下數(shù)據(jù)庫讀寫性能低下的問題,具體實(shí)現(xiàn)看各個(gè)廠商,具體到mysql的innoDB引擎中是通過快照讀來實(shí)現(xiàn)mvcc,實(shí)現(xiàn)讀寫無鎖化
快照讀
- 像不加鎖的select操作就是快照讀,即不加鎖的非阻塞讀;快照讀的前提是隔離級別不是串行級別,串行級別下的快照讀會退化成當(dāng)前讀;之所以出現(xiàn)快照讀的情況,是基于提高并發(fā)性能的考慮,快照讀的實(shí)現(xiàn)是基于多版本并發(fā)控制,即MVCC,可以認(rèn)為MVCC是行鎖的一個(gè)變種,但它在很多情況下,避免了加鎖操作,降低了開銷;既然是基于多版本,即快照讀可能讀到的并不一定是數(shù)據(jù)的最新版本,而有可能是之前的歷史版本。
- 快照讀本身也是一個(gè)抽象概念,再深入研究。MVCC模型在MySQL中的具體實(shí)現(xiàn)則是由 多個(gè)隱式字段,undo日志 ,Read View 等去完成的
當(dāng)前讀
像select lock in share mode(共享鎖), select for update ; update, insert ,delete(排他鎖)這些操作都是一種當(dāng)前讀,為什么叫當(dāng)前讀?就是它讀取的是記錄的最新版本,讀取時(shí)還要保證其他并發(fā)事務(wù)不能修改當(dāng)前記錄,會對讀取的記錄進(jìn)行加鎖。
重點(diǎn)解析讀已提交rc和可重復(fù)讀rr
讀已提交rc
- rc使用了mvcc機(jī)制,即有快照讀,那么為什么依然會出現(xiàn)幻讀和不可重復(fù)讀的問題,這個(gè)取決于快照讀中read view生成的時(shí)機(jī),根據(jù)隔離級別的定義,rc下的read view在每一次select語句時(shí)都會新生成一個(gè),所以可以讀到當(dāng)前時(shí)間點(diǎn)已提交的事務(wù)的最新數(shù)據(jù),雖然保證了數(shù)據(jù)是最新的,但是同時(shí)帶來在一個(gè)事務(wù)范圍內(nèi)同一條數(shù)據(jù)的內(nèi)容發(fā)生變化的可能(不可重復(fù)讀現(xiàn)象),也可能帶來數(shù)據(jù)行數(shù)發(fā)生增加(幻讀現(xiàn)象)
- rc下的當(dāng)前讀同rr的當(dāng)前讀
可重復(fù)讀rr
- rr使用了mvcc機(jī)制,即有快照讀,那么為什么能解決幻讀和不可重復(fù)讀的問題,這個(gè)取決于快照讀中read view生成的時(shí)機(jī),根據(jù)隔離級別的定義,rr下的read view在事務(wù)中第一次select語句時(shí)會新生成一個(gè),后續(xù)查詢均采用該read view中的數(shù)據(jù),所以可以讀到的數(shù)據(jù)永遠(yuǎn)是第一次讀到的(不同的查詢語句當(dāng)然不同,不予討論)。雖然保證了數(shù)據(jù)讀出來是不變的,但是依然不能避免其他事務(wù)做當(dāng)前讀操作導(dǎo)致的幻讀,即rr讀到的是舊的副本。算是為了提高并發(fā)讀寫場景下性能作出的犧牲吧,犧牲了一致性。
- 但是rr在當(dāng)前讀場景下通過next-key lock(行鎖+gap鎖)解決了幻讀問題,白話就是對范圍內(nèi)的數(shù)據(jù)行進(jìn)行加鎖,禁止其他事務(wù)并發(fā)寫操作,也就不存在幻讀問題了。
- 常見用法場景:某個(gè)事務(wù)內(nèi)需要先查詢,后進(jìn)行邏輯處理,最后更新數(shù)據(jù)庫,這種一般先select where xx>a and xx<b for update,這樣就鎖住了a的上個(gè)位置到b的下個(gè)位置的數(shù)據(jù)(gap鎖的定義,具體概念和細(xì)節(jié)需要額外展開講,推薦看http://www.rzrgm.cn/crazylqy/p/7611069.html)。但是缺點(diǎn)顯而易見,鎖的時(shí)間較長,取舍取決于業(yè)務(wù)。
read view
用來存儲事務(wù)相關(guān)信息,包括事務(wù)id等等,具體數(shù)據(jù)存放在undo log中。
實(shí)踐
mysql5.6版本
select @@tx_isolation;
select version();
set session tx_isolation='repeatable-read';
BEGIN;
-- select * from sys_role;
update sys_role set REMARK = 'for update3' where id = 22
select * from sys_role where id = 23 for update
COMMIT
參考
https://www.jianshu.com/p/8845ddca3b23
http://www.rzrgm.cn/jian-gao/p/10795407.html
http://www.rzrgm.cn/JMrLi/p/12705188.html