Sql請求的過程
優(yōu)化器:你以為你的sql就是你的sql?
事務
原子性
一致性
持久性
隔離性
隔離性
WHY
臟讀
原因
因為其他事務回滾導致的
讀到了并一定最終存在的數(shù)據(jù)焰雕,這就是臟讀无牵。如:
1.事務A改變了訂單表的某條數(shù)據(jù)的訂單狀態(tài)為“已發(fā)貨”
2.事務B獲取該條數(shù)據(jù)的訂單狀態(tài)為“已發(fā)貨”
3.事務A發(fā)生錯誤了,進行了回滾撵幽,訂單狀態(tài)由“已發(fā)貨”改為了“待發(fā)貨”
4.事務B拿著錯誤的“已發(fā)貨”狀態(tài)遗菠,去進行后續(xù)操作
可重復讀
是在一個事務內列肢,最開始讀到的數(shù)據(jù)和事務結束前的任意時刻讀到的同一批數(shù)據(jù)都是一致的。通常針對數(shù)據(jù)更新(UPDATE)操作官册。
如:
- 事務A先將訂單狀態(tài)由“待發(fā)貨” 改為 “已發(fā)貨”生兆,但事務A暫未提交
- 事務A內再次 讀到訂單狀態(tài),為“待發(fā)貨”
不可重復讀
原因
因為其他事務的提交導致的
對比可重復讀膝宁,不可重復讀指的是在同一事務內鸦难,不同的時刻讀到的同一批數(shù)據(jù)可能是不一樣的,可能會受到其他事務的影響员淫,比如其他事務改了這批數(shù)據(jù)并提交了合蔽。通常針對數(shù)據(jù)更新(UPDATE)操作。
如:
- 事務A先將訂單狀態(tài)由“待發(fā)貨” 改為 “已發(fā)貨”介返,但事務A暫未提交
- 事務A內再次 讀到訂單狀態(tài)拴事,為“已發(fā)貨”
幻讀
原因
因為其他事務在本事務之后開始,又在本事務結束之前結束
1.事務A將訂單狀態(tài)由“待發(fā)貨”改為“已發(fā)貨”
2.事務B將訂單狀態(tài)改為“待發(fā)貨”
3.事務B提交
4.事務A還未提前之前圣蝎,檢查訂單狀態(tài)刃宵,發(fā)現(xiàn)還是“待發(fā)貨”,像是出現(xiàn)了幻覺
隔離級別
隔離級別
讀未提交(READ UNCOMMIT)
如:
啟動兩個事務徘公,分別為事務A和事務B牲证,在事務A中使用 update 語句,修改 age 的值為10关面,初始是1 坦袍,在執(zhí)行完 update 語句之后缭裆,在事務B中查詢 user 表,會看到 age 的值已經(jīng)是 10 了澈驼,這時候事務A還沒有提交,而此時事務B有可能拿著已經(jīng)修改過的 age=10 去進行其他操作了。在事務B進行操作的過程中挎塌,很有可能事務A由于某些原因徘六,進行了事務回滾操作榴都,那其實事務B得到的就是臟數(shù)據(jù)了,拿著臟數(shù)據(jù)去進行其他的計算嘴高,那結果肯定也是有問題的竿音。
讀未提交,其實就是可以讀到其他事務未提交的數(shù)據(jù)拴驮,但沒有辦法保證你讀到的數(shù)據(jù)最終一定是提交后的數(shù)據(jù)春瞬,如果中間發(fā)生回滾套啤,那就會出現(xiàn)臟數(shù)據(jù)問題,讀未提交沒辦法解決臟數(shù)據(jù)問題萄涯。更別提可重復讀和幻讀了唆鸡,想都不要想。
讀未提交喇闸,它是性能最好,也可以說它是最野蠻的方式唆樊,因為它壓根兒就不加鎖刻蟹,所以根本談不上什么隔離效果,可以理解為沒有隔離
讀提交(READ COMMIT)
讀提交每次執(zhí)行語句的時候都要重新創(chuàng)建一次快照
同樣開啟事務A和事務B兩個事務舆瘪,在事務A中使用 update 語句將 id=1 的記錄行 age 字段改為 10。此時淀衣,在事務B中使用 select 語句進行查詢召调,我們發(fā)現(xiàn)在事務A提交之前蛮浑,事務B中查詢到的記錄 age 一直是1只嚣,直到事務A提交,此時在事務B中 select 查詢册舞,發(fā)現(xiàn) age 的值已經(jīng)是 10 了蕴掏。
這就出現(xiàn)了一個問題调鲸,在同一事務中(本例中的事務B),事務的不同時刻同樣的查詢條件藐石,查詢出來的記錄內容是不一樣的,事務A的提交影響了事務B的查詢結果,這就是不可重復讀角雷,也就是讀提交隔離級別性穿。
每個 select 語句都有自己的一份快照,而不是一個事務一份需曾,所以在不同的時刻,查詢出來的數(shù)據(jù)可能是不一致的商源。
讀提交解決了臟讀的問題谋减,但是無法做到可重復讀,也沒辦法解決幻讀
可重復讀((REAPEAT READ)默認)
僅僅在事務開始是創(chuàng)建一次快照
可重復是對比不可重復而言的庄吼,上面說不可重復讀是指同一事物不同時刻讀到的數(shù)據(jù)值可能不一致严就。而可重復讀是指,事務不會讀到其他事務對已有數(shù)據(jù)的修改渐行,及時其他事務已提交,也就是說殊轴,事務開始時讀到的已有數(shù)據(jù)是什么,在事務提交前的任意時刻樊零,這些數(shù)據(jù)的值都是一樣的孽文。但是,對于其他事務新插入的數(shù)據(jù)是可以讀到的沉衣,這也就引發(fā)了幻讀問題
MySQL 的可重復讀隔離級別其實解決了幻讀問題
串行化(SERIALIZE)
串行化是4種事務隔離級別中隔離效果最好的减牺,解決了臟讀、可重復讀肥隆、幻讀的問題稚失,但是效果最差,它將事務的執(zhí)行變?yōu)轫樞驁?zhí)行句各,與其他三個隔離級別相比,它就相當于單線程矾屯,后一個事務的執(zhí)行必須等待前一個事務結束初厚。
讀的時候加共享鎖,也就是其他事務可以并發(fā)讀骤坐,但是不能寫下愈。寫的時候加排它鎖,其他事務不能并發(fā)寫也不能并發(fā)讀
如何實現(xiàn)事務隔離
實現(xiàn)可重復讀(MVVC多版本并發(fā)控制)
我們在數(shù)據(jù)庫表中看到的一行記錄可能實際上有多個版本拌夏,每個版本的記錄除了有數(shù)據(jù)本身外,還要有一個表示版本的字段障簿,記為 row trx_id,而這個字段就是使其產(chǎn)生的事務的 id皆怕,事務 ID 記為 transaction id西篓,它在事務開始的時候向事務系統(tǒng)申請,按時間先后順序遞增
在上面介紹讀提交和可重復讀的時候都提到了一個詞虱黄,叫做快照吮成,學名叫做一致性視圖,這也是可重復讀和不可重復讀的關鍵仅醇,可重復讀是在事務開始的時候生成一個當前事務全局性的快照魔种,而讀提交則是每次執(zhí)行語句的時候都重新生成一次快照粉洼。
對于一個快照來說,它能夠讀到那些版本數(shù)據(jù)安拟,要遵循以下規(guī)則:
- 當前事務內的更新宵喂,可以讀到;
- 版本未提交拙泽,不能讀到裸燎;
- 版本已提交,但是卻在快照創(chuàng)建后提交的荷荤,不能讀到;
- 版本已提交蕴纳,且是在快照創(chuàng)建前提交的,可以讀到***
利用上面的規(guī)則翻翩,再返回去套用到讀提交和可重復讀的那兩張圖上就很清晰了喇潘。還是要強調,兩者主要的區(qū)別就是在快照的創(chuàng)建上絮吵,可重復讀僅在事務開始是創(chuàng)建一次忱屑,而讀提交每次執(zhí)行語句的時候都要重新創(chuàng)建一次
并發(fā)寫問題
存在這的情況,兩個事務伴嗡,對同一條數(shù)據(jù)做修改从铲。最后結果應該是哪個事務的結果呢,肯定要是時間靠后的那個對不對阱扬。并且更新之前要先讀數(shù)據(jù)伸辟,這里所說的讀和上面說到的讀不一樣,更新之前的讀叫做“當前讀”信夫,總是當前版本的數(shù)據(jù)静稻,也就是多版本中最新一次提交的那版。
假設事務A執(zhí)行 update 操作振湾, update 的時候要對所修改的行加行鎖,這個行鎖會在提交之后才釋放佛南。而在事務A提交之前,事務B也想 update 這行數(shù)據(jù)及穗,于是申請行鎖绵载,但是由于已經(jīng)被事務A占有,事務B是申請不到的焚虱,此時懂版,事務B就會一直處于等待狀態(tài),直到事務A提交躯畴,事務B才能繼續(xù)執(zhí)行蓬抄,如果事務A的時間太長,那么事務B很有可能出現(xiàn)超時異常嚷缭。如下圖所示。
加鎖
如果where中的字段有索引路幸,直接加鎖优床;
如果where中沒有索引誓焦,先把所有數(shù)據(jù)都加鎖杂伟,然后再把不滿足條件的數(shù)據(jù)的鎖去掉,性能不好赫粥。
解決幻讀問題
并發(fā)寫問題的解決方式就是行鎖,而解決幻讀用的也是鎖频蛔,叫做間隙鎖,
MySQL 把行鎖和間隙鎖合并在一起瀑粥,解決了并發(fā)寫和幻讀的問題三圆,這個鎖叫做 Next-Key鎖
假設現(xiàn)在表中有兩條記錄,并且 age 字段已經(jīng)添加了索引修噪,兩條記錄 age 的值分別為 10 和 30
此時路媚,在數(shù)據(jù)庫中會為索引維護一套B+樹,用來快速定位行記錄适荣。B+索引樹是有序的院领,所以會把這張表的索引分割成幾個區(qū)間。
如圖所示丈氓,分成了3 個區(qū)間万俗,(負無窮,10]饮怯、(10,30]、(30,正無窮]蓖墅,在這3個區(qū)間是可以加間隙鎖的
之后论矾,我用下面的兩個事務演示一下加鎖過程
在事務A提交之前,事務B的插入操作只能等待饱亿,這就是間隙鎖起得作用。當事務A執(zhí)行update user set name='風箏2號’ where age = 10; 的時候彪笼,由于條件 where age = 10 杰扫,數(shù)據(jù)庫不僅在 age =10 的行上添加了行鎖,而且在這條記錄的兩邊章姓,也就是(負無窮,10]凡伊、(10,30]這兩個區(qū)間加了間隙鎖,從而導致事務B插入操作無法完成系忙,只能等待事務A提交。不僅插入 age = 10 的記錄需要等待事務A提交风宁,age<10蛹疯、10<age<30 的記錄頁無法完成,而大于等于30的記錄則不受影響饮寞,這足以解決幻讀問題了列吼。
這是有索引的情況,如果 age 不是索引列慌申,那么數(shù)據(jù)庫會為整個表加上間隙鎖理郑。所以,如果是沒有索引的話类缤,不管 age 是否大于等于30邻吭,都要等待事務A提交才可以成功插入宴霸。
總結
MySQL 的 InnoDB 引擎才支持事務,其中可重復讀是默認的隔離級別畸写。
讀未提交和串行化基本上是不需要考慮的隔離級別,前者不加鎖限制论笔,后者相當于單線程執(zhí)行千所,效率太差。
讀提交解決了臟讀問題最楷,行鎖解決了并發(fā)更新的問題待错。并且 MySQL 在可重復讀級別解決了幻讀問題,是通過行鎖和間隙鎖的組合 Next-Key 鎖實現(xiàn)的犯建。