首先需要明確的就是“幻讀”概念: 隔離級別是可重復讀,在一個事務中前后兩次查詢,查到了其他事務insert進來的數(shù)據(jù)。
公司主營業(yè)務:成都網(wǎng)站建設、做網(wǎng)站、移動網(wǎng)站開發(fā)等業(yè)務。幫助企業(yè)客戶真正實現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競爭能力。創(chuàng)新互聯(lián)是一支青春激揚、勤奮敬業(yè)、活力青春激揚、勤奮敬業(yè)、活力澎湃、和諧高效的團隊。公司秉承以“開放、自由、嚴謹、自律”為核心的企業(yè)文化,感謝他們對我們的高要求,感謝他們從不同領域給我們帶來的挑戰(zhàn),讓我們激情的團隊有機會用頭腦與智慧不斷的給客戶帶來驚喜。創(chuàng)新互聯(lián)推出珠山免費做網(wǎng)站回饋大家。
強調(diào)的是讀取到了其他事務插入進來的數(shù)據(jù)。
下面來論證一下可重復讀下幻讀的解決方案
先明確一下,for update語法就是當前讀,也就是查詢當前已經(jīng)提交的數(shù)據(jù),并且是帶悲觀鎖的。沒有for update就是快照讀,也就是根據(jù)readView讀取的undolog中的數(shù)據(jù)。
如果按照以上猜想,那么整個執(zhí)行結果就違背了 可重復讀 的隔離級別了。
那么我們再假設select * from TABLE where d = 5 for update;這條語句鎖定的是所有被掃描到的數(shù)據(jù)。
這是因為T2階段的update會被阻塞住,畢竟所有被掃描到的記錄都被鎖定了。
按照上述推理過程,很顯然,即使鎖定所有掃描到的數(shù)據(jù)行,也依然存在幻讀的情況。違背了 可重復讀 的隔離級別。
針對這個情況,我們要解決幻讀的問題,那么就要求針對所有被掃描的記錄行以及還不存在的d=5的記錄行都給鎖住。
至此,當前查詢結果完全滿足 可重復讀 的隔離級別。
通過以上推論,我們可以總結一下,在可重復讀的隔離級別下,解決幻讀除了需要鎖定所有掃描到的記錄行外,還需要鎖定行之間的間隙,也就是通過間隙鎖來解決幻讀的問題。
SERIALIZABLE 采用兩階段鎖來保證隔離性
加鎖階段:只加鎖,不放鎖
解鎖階段:只放鎖,不加鎖。
且無論 讀 還是 寫 , 都要加鎖
但是這樣做了以后, 失去了MVCC的特性 (非鎖定的一致性讀)
lock table 讀鎖定
如果一個線程獲得在一個表上的read鎖,那么該線程和所有其他線程只能從表中讀數(shù)據(jù),不能進行任何寫操作。
lock tables user read;//讀鎖定表 unlock tables;//解鎖 lock tables user read local;//本地讀鎖定表,其他線程的insert未被阻塞,update操作被阻塞
lock table 寫鎖定
如果一個線程在一個表上得到一個 write鎖,那么只有擁有這個鎖的線程可以從表中讀取和寫表。其它的線程被阻塞。
lock tables user write;//寫鎖定表 unlock tables;//解鎖
Yii中的用法實例
/** * 當日單項內(nèi)容狀態(tài) */ public function getPointAready($marke,$dayTime){ $model = SysRun::model()-findByAttributes(array('syr_marking'=$marke,'syr_daytime'=$dayTime)); if(empty($model)){ //表寫鎖定 Yii::app()-db-createCommand()-setText("lock tables {{sys_run}} WRITE")-execute(); $model = new SysRun(); $model-syr_marking = $marke; $model-syr_daytime = $dayTime; $model-syr_val = 0; $model-syr_subval = 0; $model-save(); //表解鎖 Yii::app()-db-createCommand()-setText("unlock tables")-execute(); } return $model; }
更多關于Yii相關內(nèi)容感興趣的讀者可查看本站專題:《Yii框架入門及常用技巧總結》、《php優(yōu)秀開發(fā)框架總結》、《smarty模板入門基礎教程》、《php操作office文檔技巧總結(包括word,excel,access,ppt)》、《php面向對象程序設計入門教程》、《php字符串(string)用法總結》、《php+mysql數(shù)據(jù)庫操作入門教程》及《php常見數(shù)據(jù)庫操作技巧匯總》
一個事務要更新一行,如果剛好有另外一個事務擁有這一行的行鎖,會被鎖住,進入等待狀態(tài)。既然進入了等待狀態(tài),那么等到這個事務自己獲取到行鎖要更新數(shù)據(jù)的時候,它讀到的值又是什么呢?
可重復讀隔離級別下,事務在啟動的時候就“拍了個整個庫的快照”。如果一個庫有100G,那么我啟動一個事務,MySQL就要拷?100G的數(shù)據(jù)出來,這個過程得多慢啊。但是平時事務執(zhí)行起來卻是非??斓?。不是全部拷貝出來那是怎么實現(xiàn)的呢?
InnoDB里面每個事務有一個唯一的事務ID,叫作transaction id。它是在事務開始的時候向InnoDB的事務系統(tǒng)申請的,是按申請順序嚴格遞增的。
而每行數(shù)據(jù)也都是有多個版本的。每次事務更新數(shù)據(jù)的時候,都會生成一個新的數(shù)據(jù)版本,并且把transaction id賦值給這個數(shù) 據(jù)版本的事務ID,記為row trx_id。同時,舊的數(shù)據(jù)版本要保留,并且在新的數(shù)據(jù)版本中,能夠有信息可以直接拿到它。
數(shù)據(jù)表中的一行記錄,其實可能有多個版本(row),每個版本有自己的row trx_id。
圖中虛線框里是同一行數(shù)據(jù)的4個版本,當前最新版本是V4,k的值是22,它是被transaction id 為25的事務更新的,因此它的row trx_id也是25。語句更新會生成undo log(回滾日志),圖中的三個虛線箭頭,就是undo log。
按照可重復讀的定義,一個事務啟動的時候,能夠看到所有已經(jīng)提交的事務結果。但是之后,這個事務執(zhí)行期間,其他事務的更新對它不可?。
一個事務只需要在啟動的時候聲明說,“以我啟動的時刻為準,如果一個數(shù)據(jù)版本是在我啟動之前生成的,就認;如果是我啟動以后才生成的,我就不認,我必須要找到它的上一個版本”。
如果“上一個版本”也不可?,那就得繼續(xù)往前找。如果是這個事務自己更新的數(shù)據(jù),它自己還是要認的。
在實現(xiàn)上, InnoDB為每個事務構造了一個數(shù)組,用來保存這個事務啟動瞬間,當前正在“活躍”的所有事務ID?!盎钴S”指的就 是,啟動了但還沒提交。數(shù)組里面事務ID的最小值記為低水位,當前系統(tǒng)里面已經(jīng)創(chuàng)建過的事務ID的最大值加1記為高水位。 這個視圖數(shù)組和高水位,就組成了當前事務的一致性視圖(read-view)。而數(shù)據(jù)版本的可?性規(guī)則,就是基于數(shù)據(jù)的row trx_id和這個一致性視圖的對比結果得到的。
InnoDB利用了“所有數(shù)據(jù)都有多個版本”的這個特性,實現(xiàn)了“秒級創(chuàng)建快照”的能力。
回到我們最開始的表格,看看最后執(zhí)行的結果是多少。做如下假設:
事務A的視圖數(shù)組就是[99,100], 事務B的視圖數(shù)組是[99,100,101], 事務C的視圖數(shù)組是[99,100,101,102]。為了簡化分析,我先把其他干擾語句去掉,只畫出跟事務A查詢邏輯有關的操作:
第一個有效更新是事務C,把數(shù)據(jù)從(1,1)改成了(1,2)。這時候,這個數(shù)據(jù)的最新版本的row trx_id是102,而90這個版本已經(jīng)成為了歷史版本。 第二個有效更新是事務B,把數(shù)據(jù)從(1,2)改成了(1,3)。這時候,這個數(shù)據(jù)的最新版本(即row trx_id)是101,而102又成為了歷史版本。
事務B的update語句,如果按照一致性讀,好像結果不對哦?
事務B的視圖數(shù)組是先生成的,之后事務C才提交,不是應該看不?(1,2)嗎,怎么能算出(1,3)來?
事務B在更新之前查詢一次數(shù)據(jù),這個查詢返回的k的值確實是1。 但是,當它要去更新數(shù)據(jù)的時候,就不能再在歷史版本上更新了,否則事務C的更新就丟失了。因此,事務B此時的set k=k+1是在(1,2)的基礎上進行的操作。 所以,這里就用到了這樣一條規(guī)則:更新數(shù)據(jù)都是先讀后寫的,而這個讀,只能讀當前的值,稱為 “當前讀” ( current read )。
在更新的時候,當前讀拿到的數(shù)據(jù)是(1,2),更新后生成了新版本的數(shù)據(jù)(1,3),這個新版本的row trx_id是101。
所以,在執(zhí)行事務B查詢語句的時候,一看自己的版本號是101,最新數(shù)據(jù)的版本號也是101,是自己的更新,可以直接使用, 所以查詢得到的k的值是3。
select語句如果加鎖,也是當前讀。
如果把事務A的查詢語句select * from t where id=1修改一下,加上lock in share mode 或 for update,也都可以讀到版本號是101的數(shù)據(jù),返回的k的值是3。下面這兩個select語句,就是分別加了讀鎖(S鎖,共享鎖)和寫鎖(X鎖,排他鎖)。
事務C’的不同是,更新后并沒有?上提交,在它提交前,事務B的更新語句先發(fā)起了。前面說過了,雖然事務C’還沒提交,但是(1,2)這個版本也已經(jīng)生成了,并且是當前的最新版本。那么,事務B的更新語句會怎么處理呢?
兩階段鎖協(xié)議,事務C’沒提交,也就是說(1,2)這個版本上的寫鎖還沒釋放。 而事務B是當前讀,必須要讀最新版本,而且必須加鎖,因此就被鎖住了,必須等到事務C’釋放這個鎖,才能繼續(xù)它的當前讀。
回到最初的問題,事務的可重復讀的能力是怎么實現(xiàn)的?