事務(wù)的定義
事務(wù)由單獨(dú)單元的一個(gè)或多個(gè)SQL語句組成,在這個(gè)單元中,每個(gè)MySQL語句是相互依賴的。而整個(gè)單獨(dú)單元作為一個(gè)不可分割的整體,如果單元中某條SQL語句一旦執(zhí)行失敗或產(chǎn)生錯(cuò)誤,整個(gè)單元將會(huì)回滾。所有受到影響的數(shù)據(jù)將返回到事務(wù)開始以前的狀態(tài);如果單元中的所有SQL語句均執(zhí)行成功,則事務(wù)被順利執(zhí)行。
事務(wù)的特性
事務(wù)有四個(gè)特性:ACID Atomicity(原子性)、Consistency(穩(wěn)定性)、Isolation(隔離性)、Durability(可靠性)
1.原子性:一組事務(wù),要么成功;要么撤回。
2.穩(wěn)定性:有非法數(shù)據(jù)(外鍵約束之類),事務(wù)撤回。
3.隔離性:事務(wù)獨(dú)立運(yùn)行。一個(gè)事務(wù)處理后的結(jié)果,影響了其他事務(wù),那么其他事務(wù)會(huì)撤回。事務(wù)的100%隔離,需要犧牲速度。
4.可靠性:軟、硬件崩潰后,InnoDB數(shù)據(jù)表驅(qū)動(dòng)會(huì)利用日志文件重構(gòu)修改。
可靠性和高速度不可兼得, innodb_flush_log_at_trx_commit選項(xiàng) 決定什么時(shí)候把事務(wù)保存到日志里。
存儲(chǔ)引擎
MySQL的事務(wù)支持不是綁定在MySQL服務(wù)器本身,而是與存儲(chǔ)引擎相關(guān)
1.存儲(chǔ)引擎的概念:在mysql中的數(shù)據(jù)用各種不同的技術(shù)存儲(chǔ)在文件(或內(nèi)存)中。
這些技術(shù)中的每一種技術(shù)都使用不同的存儲(chǔ)機(jī)制,索引 技巧,并且最終提供廣泛的不同的功能和能力??梢酝ㄟ^選擇不同的技術(shù),可以獲得額外的速度或功能,從而改善應(yīng)用的整體功能。
2.這些不同的技術(shù)以及配套的相關(guān)功能在mysql中被稱為存儲(chǔ)引擎(也稱為表類型)。
3.通過show engines;來查看mysql支持的存儲(chǔ)引擎。
4. 在mysql中用的最多的存儲(chǔ)引擎有:innodb,bdb,myisam ,memory 等。其中innodb和bdb支持事務(wù)而myisam等不支持事務(wù)。
事務(wù)的隔離
事務(wù)隔離級(jí)別:
1.Read Uncommitted(讀取未提交內(nèi)容)
在該隔離級(jí)別,所有事務(wù)都可以看到其他未提交事務(wù)的執(zhí)行結(jié)果。本隔離級(jí)別很少用于實(shí)際應(yīng)用,因?yàn)樗男阅芤膊槐绕渌?jí)別好多少。讀取未提交的數(shù)據(jù),也被稱之為臟讀(Dirty Read)。
2.Read Committed(讀取提交內(nèi)容)
這是大多數(shù)數(shù)據(jù)庫系統(tǒng)的默認(rèn)隔離級(jí)別(但不是MySQL默認(rèn)的)。它滿足了隔離的簡單定義:一個(gè)事務(wù)只能看見已經(jīng)提交事務(wù)所做的改變。這種隔離級(jí)別 也支持所謂的不可重復(fù)讀(Nonrepeatable Read),因?yàn)橥皇聞?wù)的其他實(shí)例在該實(shí)例處理其間可能會(huì)有新的commit,所以同一select可能返回不同結(jié)果。
3.Repeatable Read(可重讀)
這是MySQL的默認(rèn)事務(wù)隔離級(jí)別,它確保同一事務(wù)的多個(gè)實(shí)例在并發(fā)讀取數(shù)據(jù)時(shí),會(huì)看到同樣的數(shù)據(jù)行。不過理論上,這會(huì)導(dǎo)致另一個(gè)棘手的問題:
<幻讀(Phantom Read)?;米x的解釋有很多種,其中一種幻讀的解釋指當(dāng)用戶讀取某一范圍的數(shù)據(jù)行時(shí),另一個(gè)事務(wù)又在該范圍內(nèi)插入了新行,當(dāng)用戶再讀取該范圍的數(shù)據(jù)行時(shí),會(huì)發(fā)現(xiàn)有新的“幻影” 行。InnoDB和Falcon存儲(chǔ)引擎通過多版本并發(fā)控制(MVCC,Multiversion Concurrency Control)機(jī)制解決了該問題;還有一種解釋將幻讀與不可重復(fù)讀歸為一類。還有一個(gè)解釋為: 是指當(dāng)事務(wù)不是獨(dú)立執(zhí)行時(shí)發(fā)生的一種現(xiàn)象,例如第一個(gè)事務(wù)對(duì)一個(gè)表中的數(shù)據(jù)進(jìn)行了修改,這種修改涉及到表中的全部數(shù)據(jù)行。同時(shí),第二個(gè)事務(wù)也修改這個(gè)表中的數(shù)據(jù),這種修改是向表中插入一行新數(shù)據(jù)。那么,以后就會(huì)發(fā)生操作第一個(gè)事務(wù)的用戶發(fā)現(xiàn)表中還有沒有修改的數(shù)據(jù)行,就好象發(fā)生了幻覺一樣。
4.Serializable(可串行化)
這是最高的隔離級(jí)別,它通過強(qiáng)制事務(wù)排序,使之不可能相互沖突,從而解決幻讀問題。
簡言之,它是在每個(gè)讀的數(shù)據(jù)行上加上共享鎖。在這個(gè)級(jí)別,可能導(dǎo)致大量的超時(shí)現(xiàn)象和鎖競爭。
事務(wù)隔離模式查看和設(shè)置
查看:select @@tx_isolation;
設(shè)置:
1、SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE
1、不帶SESSION、GLOBAL的SET命令
只對(duì)下一個(gè)事務(wù)有效
2、SET SESSION
為當(dāng)前會(huì)話設(shè)置隔離模式
3、SET GLOBAL
為以后新建的所有MYSQL連接設(shè)置隔離模式(當(dāng)前連接不包括在內(nèi))
2、set [session.|global.]tx_isolation='READ-UNCOMMITTED' | 'READ-COMMITTED' | 'REPEATABLE-READ' | 'SERIALIZABLE'
MYSQL的事務(wù)處理方法
1.用begin/start transaction,rollback,commit來實(shí)現(xiàn),begin或者start transaction開始一個(gè)事務(wù),rollback事務(wù)回滾,commit事務(wù)確認(rèn)
2.直接用set來改變mysql的自動(dòng)提交模式,mysql默認(rèn)是自動(dòng)提交的,也就是你提交一個(gè)query,就直接執(zhí)行!
可以通過set autocommit = 0 禁止自動(dòng)提交set autocommit = 1 開啟自動(dòng)提交來實(shí)現(xiàn)事務(wù)的處理。
要注意當(dāng)用set autocommit = 0 的時(shí)候,你以后所有的sql都將作為事務(wù)處理,直到你用commit確認(rèn)或 rollback結(jié)束,注意當(dāng)你結(jié)束這個(gè)事務(wù)的同時(shí)也開啟了新的事務(wù)!按第一種方法只將當(dāng)前的做為一個(gè)事務(wù)!
四種隔離級(jí)別的測試
打開兩個(gè)mysql的連接,進(jìn)入到對(duì)應(yīng)的數(shù)據(jù)庫test中,測試的表為example,字段為id(int),name(char),status(int);
Read Uncommitted
| 測試步驟 | conn1 | conn2 | 結(jié)果 |
|---|---|---|---|
| 1 | begin; | begin; | |
| 2 | select * from example; | 空 | |
| 3 | select * from example; | 空 | |
| 4 | insert into example (name,status) value ('a',1); | ||
| 5 | select * from example; | 有數(shù)據(jù) | |
| 6 | select * from example; | 有數(shù)據(jù)(臟讀) | |
| 7 | rollback; | ||
| 8 | select * from example; | 沒有數(shù)據(jù) | |
| 9 | select * from example; | 沒有數(shù)據(jù) |
Read committed
| 測試步驟 | conn1 | conn2 | 結(jié)果 |
|---|---|---|---|
| 1 | begin; | begin; | |
| 2 | select * from example; | 空 | |
| 3 | select * from example; | 空 | |
| 4 | insert into example (name,status) value ('a',1); | ||
| 5 | select * from example; | 有數(shù)據(jù) | |
| 6 | select * from example; | 沒有數(shù)據(jù) | |
| 7 | commit; | ||
| 8 | select * from example; | 有數(shù)據(jù) | |
| 9 | select * from example; | 有數(shù)據(jù) |
Repeatable read
| 測試步驟 | conn1 | conn2 | 結(jié)果 |
|---|---|---|---|
| 1 | begin; | begin; | |
| 2 | select * from example; | 有1條數(shù)據(jù) | |
| 3 | select * from example; | 有1條數(shù)據(jù) | |
| 4 | insert into example (name,status) value ('b',1); | ||
| 5 | select * from example; | 有2條數(shù)據(jù) | |
| 6 | select * from example; | 有1條數(shù)據(jù) | |
| 7 | commit; | ||
| 8 | select * from example; | 有1條數(shù)據(jù) | |
| 9 | commit; | ||
| 10 | select * from example; | 有2條數(shù)據(jù) |
Serializable
例1:
| 測試步驟 | conn1 | conn2 | 結(jié)果 |
|---|---|---|---|
| 1 | begin; | begin; | |
| 2 | select * from example; | 有2條數(shù)據(jù) | |
| 3 | select * from example; | 有2條數(shù)據(jù) | |
| 4 | insert into example (name,status) value ('c',1); | 一直等待中 | |
| 5 | commit; | ||
| 6 | conn1中執(zhí)行成功 | ||
| 7 | commit; | ||
| 8 | select * from example; | 有3條數(shù)據(jù) | |
| 9 | select * from example; | 有3條數(shù)據(jù) |
例2:
| 測試步驟 | conn1 | conn2 | 結(jié)果 |
|---|---|---|---|
| 1 | begin; | begin; | |
| 2 | select * from example; | 有3條數(shù)據(jù) | |
| 4 | insert into example (name,status) value ('d',1); | ||
| 3 | select * from example; | 一直等待中 | |
| 5 | commit; | ||
| 6 | conn2中查詢成功 | ||
| 7 | commit; | ||
| 8 | select * from example; | 有4條數(shù)據(jù) | |
| 9 | select * from example; | 有4條數(shù)據(jù) |
數(shù)據(jù)庫封鎖技術(shù)
為了體現(xiàn)隔離級(jí)別,數(shù)據(jù)庫使用了封鎖技術(shù)(locking)
(1)S鎖,Share Locks,共享鎖,讀鎖,被加鎖的對(duì)象可以被持鎖事務(wù)讀取,但是不能被修改,其他事務(wù)也可以在上面再加s鎖。(2)X鎖,Exclusive Locks,排他鎖,寫鎖,被加鎖的對(duì)象只能被持有鎖的事務(wù)讀取和修改,其他事務(wù)無法在該對(duì)象上加其他鎖,也不能讀取和修改該對(duì)象。
引入封鎖技術(shù)又帶來了“死鎖”問題
解決死鎖的兩類方法:(1)預(yù)防法:一次封鎖法(每個(gè)事務(wù)必須將所用到的數(shù)據(jù)全部加鎖,否則不能執(zhí)行)和順序封鎖法(對(duì)用到的數(shù)據(jù)按照預(yù)先設(shè)定的順序加鎖)。(2)診斷解除法:超時(shí)法(一事務(wù)超過規(guī)定時(shí)間則判定發(fā)生死鎖)和等待圖法(事務(wù)等待圖是一個(gè)有向圖G=(T,U),T為結(jié)點(diǎn)的集合,每個(gè)結(jié)點(diǎn)表示正在運(yùn)行的事務(wù);U為邊的集合,每條邊表示事務(wù)等待的情況。若事務(wù)T1等待事務(wù)T2,則T1,T2之間有一條有向邊,從 T1 指向 T2。如果發(fā)現(xiàn)圖中存在回路,則表示系統(tǒng)中出現(xiàn)了死鎖)。
封鎖協(xié)議(Locking Protocol)
保證數(shù)據(jù)一致性的封鎖協(xié)議的三級(jí)封鎖協(xié)議
對(duì)并發(fā)操作的不正確調(diào)度可能會(huì)帶來的三種數(shù)據(jù)不一致性:丟失修改、不可重復(fù)讀和讀“臟”數(shù)據(jù)。三級(jí)封鎖協(xié)議分別在不同程度上解決了這一問題。(1)1級(jí)封鎖協(xié)議:事務(wù)T修改數(shù)據(jù)R之前必須先對(duì)其X加鎖,直到事務(wù)結(jié)束才釋放。事務(wù)結(jié)束包括正常結(jié)束和非正常結(jié)束。1級(jí)封鎖協(xié)議可防止丟失修改,并保證事務(wù)T是可恢復(fù)的。在1級(jí)封鎖協(xié)議中,如果僅僅是讀數(shù)據(jù)不對(duì)其進(jìn)行修改,是不需要加鎖的,所以它不能保證可重復(fù)讀和不讀“臟”數(shù)據(jù)。(2)2級(jí)封鎖協(xié)議:1級(jí)封鎖協(xié)議加上事務(wù)T在讀取數(shù)據(jù)R之前必須先對(duì)其加S鎖,讀完后即可釋放S鎖。2級(jí)封鎖協(xié)議除防止丟失修改,還可進(jìn)一步防止讀“臟”數(shù)據(jù)。在2級(jí)封鎖協(xié)議中,由于讀完數(shù)據(jù)后即可釋放S鎖,所以它不能保證保重復(fù)讀。(3)3級(jí)封鎖協(xié)議:1級(jí)封鎖協(xié)議加上事務(wù)T在讀取數(shù)據(jù)R之前必須先對(duì)其加S鎖,直到事務(wù)結(jié)果才釋放。3級(jí)封鎖協(xié)議除防止丟失修改和不讀“臟”數(shù)據(jù)外,還進(jìn)一步防止了不可重復(fù)讀。
保證并行調(diào)度可串行性的封鎖協(xié)議的兩段鎖協(xié)議
可串行性是并行調(diào)度正確性的唯一準(zhǔn)則,兩段鎖(簡稱2PL)協(xié)議是為保證并行調(diào)度可串行性而提供的封鎖協(xié)議。兩段鎖協(xié)議規(guī)定:在對(duì)任何數(shù)據(jù)進(jìn)行讀、寫操作之前,事務(wù)道首先要獲得對(duì)該數(shù)據(jù)的封鎖,而且在釋放一個(gè)封鎖之生,事務(wù)不再獲得任何其他封鎖。所謂“兩段”鎖的含義是,事務(wù)分為兩個(gè)階段,第一階段是獲得封鎖,也稱為擴(kuò)展階段,第二階段是釋放封鎖,也稱為收縮階段。
可串行化
調(diào)度是一個(gè)或多個(gè)事務(wù)的重要操作按時(shí)間排序的一個(gè)序列。如果一個(gè)調(diào)度的動(dòng)作首先是一個(gè)事務(wù)的所有動(dòng)作,然后是另一個(gè)事務(wù)的所有動(dòng)作,以此類推,而沒有動(dòng)作的混合,那么我們說這一調(diào)度是串行的。事務(wù)的正確性原則告訴我們,每個(gè)串行調(diào)度都將保持?jǐn)?shù)據(jù)庫狀態(tài)的一致性。 通常,不管數(shù)據(jù)庫初態(tài)怎樣,一個(gè)調(diào)度對(duì)數(shù)據(jù)庫狀態(tài)的影響都和某個(gè)串行調(diào)度相同,我們就說這個(gè)調(diào)度是可串行化的。[如果一并行調(diào)度的結(jié)果等價(jià)于某一串行調(diào)度的結(jié)果,那么這個(gè)并行調(diào)度成為可串行化的]
在并發(fā)控制中采用封鎖協(xié)議解決數(shù)據(jù)的不一致性并發(fā)控制的主要方法是封鎖(Locking)。就是要用正確的方式調(diào)度并發(fā)操作,使一個(gè)用戶的事務(wù)在執(zhí)行過程中不受其他事務(wù)的干擾,從而避免造成數(shù)據(jù)的不一致性。封鎖是使事務(wù)對(duì)它要操作的數(shù)據(jù)有一定的控制能力。封鎖通常具有3 個(gè)環(huán)節(jié):第一個(gè)環(huán)節(jié)是申請(qǐng)加鎖,即事務(wù)在操作前要對(duì)它將使用的數(shù)據(jù)提出加鎖申請(qǐng); 第二個(gè)環(huán)節(jié)是獲得鎖,即當(dāng)條件成熟時(shí),系統(tǒng)允許事務(wù)對(duì)數(shù)據(jù)進(jìn)行加鎖,從而事務(wù)獲得數(shù)據(jù)的控制權(quán);第三個(gè)環(huán)節(jié)是釋放鎖,即完成操作后事務(wù)放棄數(shù)據(jù)的控制權(quán)。
封鎖是實(shí)現(xiàn)并發(fā)控制的一個(gè)非常重要的技術(shù)。所謂封鎖就是事務(wù)T在對(duì)某個(gè)數(shù)據(jù)對(duì)象例如表、記錄等操作之前,先向系統(tǒng)發(fā)出請(qǐng)求,對(duì)其加鎖。加鎖后事務(wù)T就對(duì)該 數(shù)據(jù)對(duì)象有了一定的控制,在事務(wù)T釋放它的鎖之前,其它的事務(wù)不能更新此數(shù)據(jù)對(duì)象。 基本的封鎖類型有兩種:排它鎖(Exclusive locks 簡記為X鎖)和共享鎖(Share locks 簡記為S鎖)。
排它鎖又稱為寫鎖。若事務(wù)T對(duì)數(shù)據(jù)對(duì)象A加上X鎖,則只允許T讀取和修改A,其它任何事務(wù)都不能再對(duì)A加任何類型的鎖,直到T釋放A上的鎖。這就保證了其它事務(wù)在T釋放A上的鎖之前不能再讀取和修改A。 共享鎖又稱為讀鎖。若事務(wù)T對(duì)數(shù)據(jù)對(duì)象A加上S鎖,則其它事務(wù)只能再對(duì)A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。這就保證了其它事務(wù)可以讀A,但在T釋放A上的S鎖之前不能對(duì)A做任何修改。
在運(yùn)用X鎖和S鎖這兩種基本封鎖,對(duì)數(shù)據(jù)對(duì)象加鎖時(shí),還需要約定一些規(guī)則,例如應(yīng)何時(shí)申請(qǐng)X鎖或S鎖、持鎖時(shí)間、何時(shí)釋放等。我們稱這些規(guī)則為封鎖協(xié)議 (Locking Protocol)。對(duì)封鎖方式規(guī)定不同的規(guī)則,就形成了各種不同的封鎖協(xié)議。下面介紹三級(jí)封鎖協(xié)議。三級(jí)封鎖協(xié)議分別在不同程度上解決了丟失的修改、不 可重復(fù)讀和讀"臟"數(shù)據(jù)等不一致性問題,為并發(fā)操作的正確調(diào)度提供一定的保證。下面只給出三級(jí)封鎖協(xié)議的定義,不再做過多探討。
1級(jí)封鎖協(xié)議是:事務(wù)T在修改數(shù)據(jù)R之前必須先對(duì)其加X鎖,直到事務(wù)結(jié)束才釋放。事務(wù)結(jié)束包括正常結(jié)束(COMMIT)和非正常結(jié)束(ROLLBACK)。1級(jí)封鎖協(xié)議可防止丟失修改,并保證事務(wù)T是可恢復(fù)的。在1級(jí)封鎖協(xié)議中,如果僅僅是讀數(shù)據(jù)不對(duì)其進(jìn)行修改,是不需要加鎖的,所以它不能保證可重復(fù)讀和不 讀"臟"數(shù)據(jù)。
2級(jí)封鎖協(xié)議是:1級(jí)封鎖協(xié)議加上事務(wù)T在讀取數(shù)據(jù)R之前必須先對(duì)其加S鎖,讀完后即可釋放S鎖。2級(jí)封鎖協(xié)議除防止了丟失修改,還可進(jìn)一步防止讀"臟"數(shù)據(jù)。
3級(jí)封鎖協(xié)議是:1級(jí)封鎖協(xié)議加上事務(wù)T在讀取數(shù)據(jù)R之前必須先對(duì)其加S鎖,直到事務(wù)結(jié)束才釋放。3級(jí)封鎖協(xié)議除防止了丟失修改和不讀'臟'數(shù)據(jù)外,還進(jìn)一步防止了不可重復(fù)讀。執(zhí)行了封鎖協(xié)議之后,就可以克服數(shù)據(jù)庫操作中的數(shù)據(jù)不一致所引起的問題。
事務(wù)鎖定模式
系統(tǒng)默認(rèn): 不需要等待某事務(wù)結(jié)束,可直接查詢到結(jié)果,但不能再進(jìn)行修改、刪除。
缺點(diǎn):查詢到的結(jié)果,可能是已經(jīng)過期的。
優(yōu)點(diǎn):不需要等待某事務(wù)結(jié)束,可直接查詢到結(jié)果。
需要用以下模式來設(shè)定鎖定模式
1、SELECT …… LOCK IN SHARE MODE(共享鎖)
查詢到的數(shù)據(jù),就是數(shù)據(jù)庫在這一時(shí)刻的數(shù)據(jù)(其他已commit事務(wù)的結(jié)果,已經(jīng)反應(yīng)到這里了)
SELECT 必須等待,某個(gè)事務(wù)結(jié)束后才能執(zhí)行
2、SELECT …… FOR UPDATE(排它鎖)
例如 SELECT * FROM tablename WHERE id<200
那么id<200的數(shù)據(jù),被查詢到的數(shù)據(jù),都將不能再進(jìn)行修改、刪除、SELECT …… LOCK IN SHARE MODE操作
一直到此事務(wù)結(jié)束
共享鎖 和 排它鎖 的區(qū)別:在于是否阻斷其他客戶發(fā)出的 SELECT …… LOCK IN SHARE MODE命令
3、INSERT / UPDATE / DELETE
所有關(guān)聯(lián)數(shù)據(jù)都會(huì)被鎖定,加上排它鎖
4、防插入鎖
例如 SELECT * FROM tablename WHERE id>200
那么id>200的記錄無法被插入
5、死鎖
自動(dòng)識(shí)別死鎖
先進(jìn)來的進(jìn)程被執(zhí)行,后來的進(jìn)程收到出錯(cuò)消息,并按ROLLBACK方式回滾
innodb_lock_wait_timeout = n 來設(shè)置最長等待時(shí)間,默認(rèn)是50秒
MySQL中一致性非鎖定讀
一致性非鎖定讀(consistent nonlocking read)是指InnoDB存儲(chǔ)引擎通過多版本控制(multi versionning)的方式來讀取當(dāng)前執(zhí)行時(shí)間數(shù)據(jù)庫中行的數(shù)據(jù),如果讀取的行正在執(zhí)行DELETE或UPDATE操作,這是讀取操作不會(huì)因此等待行上鎖的釋放。相反的,InnoDB會(huì)去讀取行的一個(gè)快照數(shù)據(jù)。
快照數(shù)據(jù)是指該行之前版本的數(shù)據(jù),該實(shí)現(xiàn)是通過undo段來完成。而undo用來事務(wù)中的回滾數(shù)據(jù),因此快照數(shù)據(jù)本身沒有額外的開銷,此外,讀取快照數(shù)據(jù)不需要上鎖,因?yàn)闆]有事務(wù)需要對(duì)歷史數(shù)據(jù)進(jìn)行修改操作
可以看到,非鎖定讀機(jī)制極大地提高了數(shù)據(jù)庫的并發(fā)性,在InnoDB存儲(chǔ)引擎的默認(rèn)設(shè)置下,這是默認(rèn)的讀寫方式,即讀不會(huì)占用和等待表上的鎖。但是在不同的事務(wù)隔離級(jí)別下,讀取的方式不同,并不是每個(gè)事務(wù)隔離級(jí)別下都是采用非鎖定的一致性讀,此外,即使使用非鎖定的一致性讀,但是對(duì)于快照數(shù)據(jù)的定義也各不相同
快照其實(shí)是當(dāng)前行數(shù)據(jù)之前的歷史版本,每行記錄可能有多個(gè)版本,如圖顯示,一個(gè)行記錄可能有不止一個(gè)快照數(shù)據(jù),一般稱這種技術(shù)為多版本技術(shù),因此帶來的并發(fā)控制。稱為多版本并發(fā)控制(Multi Version Concurrency Control,MVCC)
在事務(wù)隔離級(jí)別RC和RR下,InnoDB存儲(chǔ)引擎引擎使用非鎖定的一致性讀。然而,對(duì)于快照數(shù)據(jù)的定義卻不相同。在rc事務(wù)隔離級(jí)別下,對(duì)于快照數(shù)據(jù),非一致性讀總是被鎖定行的最新一份快照數(shù)據(jù).而在RR事務(wù)隔離級(jí)別下,對(duì)于快照數(shù)據(jù),非一致性讀總是讀取事務(wù)開始時(shí)的行數(shù)據(jù)版本。
在可重讀事務(wù)級(jí)別中
如果使用普通的讀,會(huì)得到一致性的結(jié)果,如果使用了加鎖的讀,就會(huì)讀到“最新的”“提交”讀的結(jié)果。
本身,可重復(fù)讀和提交讀是矛盾的。在同一個(gè)事務(wù)里,如果保證了可重復(fù)讀,就會(huì)看不到其他事務(wù)的提交,違背了提交讀;如果保證了提交讀,就會(huì)導(dǎo)致前后兩次讀到的結(jié)果不一致,違背了可重復(fù)讀。
可以這么講,InnoDB提供了這樣的機(jī)制,在默認(rèn)的可重復(fù)讀的隔離級(jí)別里,可以使用加鎖讀去查詢最新的數(shù)據(jù)。
SELECT * FROM t_bitfly LOCK IN SHARE MODE;
或者SELECT * FROM child WHERE id > 100 FOR UPDATE,這樣,InnoDB會(huì)給id大于100的行(假如child表里有一行id為102),以及100-102,102+的gap都加上鎖。
需要注意,使用加鎖讀后,表被鎖住的部分在加鎖讀的事務(wù)結(jié)束前將無法更新
持續(xù)讀意味著InnoDB使用它的多版本化來給一個(gè)查詢展示某個(gè)時(shí)間點(diǎn)處數(shù)據(jù)庫的快照。查詢看到在那個(gè)時(shí)間點(diǎn)之前被提交的那些確切事務(wù)做的更改,并且沒有其后的事務(wù)或未提交事務(wù)做的改變。這個(gè)規(guī)則的例外是,查詢看到發(fā)布該查詢的事務(wù)本身所做的改變。
如果你運(yùn)行在默認(rèn)的REPEATABLE READ隔離級(jí)別,則在同一事務(wù)內(nèi)的所有持續(xù)讀讀取由該事務(wù)中第一個(gè)這樣的讀所確立的快照。你可以通過提交當(dāng)前事務(wù)并在發(fā)布新查詢的事務(wù)之后,為你的查詢獲得一個(gè)更新鮮的快照。
持續(xù)讀是默認(rèn)模式,在其中InnoDBzai在READ COMMITTED和REPEATABLE READ隔離級(jí)別處理SELECT語句。持續(xù)讀不在任何它訪問的表上設(shè)置鎖定,因此,其它用戶可自由地在持續(xù)讀在一個(gè)表上執(zhí)行的同一時(shí)間修改這些表。
注意,持續(xù)讀不在DROP TABLE和ALTER TABLE上作用。持續(xù)讀不在DROP TABLE上作用,因?yàn)镸ySQL不能使用已經(jīng)被移除的表,并且InnoDB 破壞了該表。持續(xù)讀不在ALTER TABLE上作用,因?yàn)樗谀呈聞?wù)內(nèi)執(zhí)行,該事務(wù)創(chuàng)建一個(gè)新表,并且從舊表往新表中插入行。現(xiàn)在,當(dāng)你重新發(fā)出持續(xù)讀之時(shí),它不能在新表中看見任何行,因?yàn)樗鼈儽徊迦氲揭粋€(gè)在持續(xù)讀讀取的快照中不可見的事務(wù) 里。
/* ==================== MySQL InnoDB 鎖表與鎖行 ======================== */
由于InnoDB預(yù)設(shè)是Row-Level Lock,所以只有「明確」的指定主鍵,MySQL才會(huì)執(zhí)行Row lock (只鎖住被選取的資料例) ,否則MySQL將會(huì)執(zhí)行Table Lock (將整個(gè)資料表單給鎖住)。
舉個(gè)例子: 假設(shè)有個(gè)表單products ,里面有id跟name二個(gè)欄位,id是主鍵。
例1: (明確指定主鍵,并且有此筆資料,row lock)
復(fù)制代碼代碼如下:
SELECT * FROM products WHERE id='3' FOR UPDATE;SELECT * FROM products WHERE id='3' and type=1 FOR UPDATE;
例2: (明確指定主鍵,若查無此筆資料,無lock)
復(fù)制代碼代碼如下:
SELECT * FROM products WHERE id='-1' FOR UPDATE;
例3: (無主鍵,table lock)
復(fù)制代碼代碼如下:
SELECT * FROM products WHERE name='Mouse' FOR UPDATE;
例4: (主鍵不明確,table lock)
復(fù)制代碼代碼如下:
SELECT * FROM products WHERE id<>'3' FOR UPDATE;
例5: (主鍵不明確,table lock)
復(fù)制代碼代碼如下:
SELECT * FROM products WHERE id LIKE '3' FOR UPDATE;
注1: FOR UPDATE僅適用于InnoDB,且必須在交易區(qū)塊(BEGIN/COMMIT)中才能生效。注2: 要測試鎖定的狀況,可以利用MySQL的Command Mode ,開二個(gè)視窗來做測試。
在MySql 5.0中測試確實(shí)是這樣的
另外:MyAsim 只支持表級(jí)鎖,InnerDB支持行級(jí)鎖添加了(行級(jí)鎖/表級(jí)鎖)鎖的數(shù)據(jù)不能被其它事務(wù)再鎖定,也不被其它事務(wù)修改(修改、刪除)是表級(jí)鎖時(shí),不管是否查詢到記錄,都會(huì)鎖定表此外,如果A與B都對(duì)表id進(jìn)行查詢但查詢不到記錄,則A與B在查詢上不會(huì)進(jìn)行row鎖,但A與B都會(huì)獲取排它鎖,此時(shí)A再插入一條記錄的話則會(huì)因?yàn)锽已經(jīng)有鎖而處于等待中,此時(shí)B再插入一條同樣的數(shù)據(jù)則會(huì)拋出Deadlock found when trying to get lock; try restarting transaction然后釋放鎖,此時(shí)A就獲得了鎖而插入成功
樂觀鎖與悲觀鎖
悲觀鎖
在關(guān)系數(shù)據(jù)庫管理系統(tǒng)里,悲觀并發(fā)控制(又名“悲觀鎖”,Pessimistic Concurrency Control,縮寫“PCC”)是一種并發(fā)控制的方法。它可以阻止一個(gè)事務(wù)以影響其他用戶的方式來修改數(shù)據(jù)。如果一個(gè)事務(wù)執(zhí)行的操作都某行數(shù)據(jù)應(yīng)用了鎖,那只有當(dāng)這個(gè)事務(wù)把鎖釋放,其他事務(wù)才能夠執(zhí)行與該鎖沖突的操作。悲觀并發(fā)控制主要用于數(shù)據(jù)爭用激烈的環(huán)境,以及發(fā)生并發(fā)沖突時(shí)使用鎖保護(hù)數(shù)據(jù)的成本要低于回滾事務(wù)的成本的環(huán)境中。
悲觀鎖,正如其名,它指的是對(duì)數(shù)據(jù)被外界(包括本系統(tǒng)當(dāng)前的其他事務(wù),以及來自外部系統(tǒng)的事務(wù)處理)修改持保守態(tài)度(悲觀),因此,在整個(gè)數(shù)據(jù)處理過程中,將數(shù)據(jù)處于鎖定狀態(tài)。 悲觀鎖的實(shí)現(xiàn),往往依靠數(shù)據(jù)庫提供的鎖機(jī)制 (也只有數(shù)據(jù)庫層提供的鎖機(jī)制才能真正保證數(shù)據(jù)訪問的排他性,否則,即使在本系統(tǒng)中實(shí)現(xiàn)了加鎖機(jī)制,也無法保證外部系統(tǒng)不會(huì)修改數(shù)據(jù))
在數(shù)據(jù)庫中,悲觀鎖的流程如下:
在對(duì)任意記錄進(jìn)行修改前,先嘗試為該記錄加上排他鎖(exclusive locking)。
如果加鎖失敗,說明該記錄正在被修改,那么當(dāng)前查詢可能要等待或者拋出異常。 具體響應(yīng)方式由開發(fā)者根據(jù)實(shí)際需要決定。
如果成功加鎖,那么就可以對(duì)記錄做修改,事務(wù)完成后就會(huì)解鎖了。
其間如果有其他對(duì)該記錄做修改或加排他鎖的操作,都會(huì)等待我們解鎖或直接拋出異常。
優(yōu)點(diǎn)與不足
悲觀并發(fā)控制實(shí)際上是“先取鎖再訪問”的保守策略,為數(shù)據(jù)處理的安全提供了保證。但是在效率方面,處理加鎖的機(jī)制會(huì)讓數(shù)據(jù)庫產(chǎn)生額外的開銷,還有增加產(chǎn)生死鎖的機(jī)會(huì);另外,在只讀型事務(wù)處理中由于不會(huì)產(chǎn)生沖突,也沒必要使用鎖,這樣做只能增加系統(tǒng)負(fù)載;還有會(huì)降低了并行性,一個(gè)事務(wù)如果鎖定了某行數(shù)據(jù),其他事務(wù)就必須等待該事務(wù)處理完才可以處理那行數(shù)
樂觀鎖
在關(guān)系數(shù)據(jù)庫管理系統(tǒng)里,樂觀并發(fā)控制(又名“樂觀鎖”,Optimistic Concurrency Control,縮寫“OCC”)是一種并發(fā)控制的方法。它假設(shè)多用戶并發(fā)的事務(wù)在處理時(shí)不會(huì)彼此互相影響,各事務(wù)能夠在不產(chǎn)生鎖的情況下處理各自影響的那部分?jǐn)?shù)據(jù)。在提交數(shù)據(jù)更新之前,每個(gè)事務(wù)會(huì)先檢查在該事務(wù)讀取數(shù)據(jù)后,有沒有其他事務(wù)又修改了該數(shù)據(jù)。如果其他事務(wù)有更新的話,正在提交的事務(wù)會(huì)進(jìn)行回滾。樂觀事務(wù)控制最早是由孔祥重(H.T.Kung)教授提出。
樂觀鎖( Optimistic Locking ) 相對(duì)悲觀鎖而言,樂觀鎖假設(shè)認(rèn)為數(shù)據(jù)一般情況下不會(huì)造成沖突,所以在數(shù)據(jù)進(jìn)行提交更新的時(shí)候,才會(huì)正式對(duì)數(shù)據(jù)的沖突與否進(jìn)行檢測,如果發(fā)現(xiàn)沖突了,則讓返回用戶錯(cuò)誤的信息,讓用戶決定如何去做。
相對(duì)于悲觀鎖,在對(duì)數(shù)據(jù)庫進(jìn)行處理的時(shí)候,樂觀鎖并不會(huì)使用數(shù)據(jù)庫提供的鎖機(jī)制。一般的實(shí)現(xiàn)樂觀鎖的方式就是記錄數(shù)據(jù)版本。
數(shù)據(jù)版本,為數(shù)據(jù)增加的一個(gè)版本標(biāo)識(shí)。當(dāng)讀取數(shù)據(jù)時(shí),將版本標(biāo)識(shí)的值一同讀出,數(shù)據(jù)每更新一次,同時(shí)對(duì)版本標(biāo)識(shí)進(jìn)行更新。當(dāng)我們提交更新的時(shí)候,判斷數(shù)據(jù)庫表對(duì)應(yīng)記錄的當(dāng)前版本信息與第一次取出來的版本標(biāo)識(shí)進(jìn)行比對(duì),如果數(shù)據(jù)庫表當(dāng)前版本號(hào)與第一次取出來的版本標(biāo)識(shí)值相等,則予以更新,否則認(rèn)為是過期數(shù)據(jù)。
實(shí)現(xiàn)數(shù)據(jù)版本有兩種方式,第一種是使用版本號(hào),第二種是使用時(shí)間戳。
優(yōu)點(diǎn)與不足
樂觀并發(fā)控制相信事務(wù)之間的數(shù)據(jù)競爭(data race)的概率是比較小的,因此盡可能直接做下去,直到提交的時(shí)候才去鎖定,所以不會(huì)產(chǎn)生任何鎖和死鎖。
樂觀鎖失效
樂觀鎖存在失效的情況,屬小概率事件,需要多個(gè)條件共同配合才會(huì)出現(xiàn)。如:
應(yīng)用采用自己的策略管理主鍵ID。如,常見的取當(dāng)前ID字段的最大值+1作為新ID。
版本號(hào)字段 ver
默認(rèn)值為 0 。
用戶A讀取了某個(gè)記錄準(zhǔn)備修改它。該記錄正好是ID最大的記錄,且之前沒被修改過, ver 為默認(rèn)值 0。
在用戶A讀取完成后,用戶B恰好刪除了該記錄。之后,用戶C又插入了一個(gè)新記錄。
此時(shí),陰差陽錯(cuò)的,新插入的記錄的ID與用戶A讀取的記錄的ID是一致的, 而版本號(hào)兩者又都是默認(rèn)值 0。
用戶A在用戶C操作完成后,修改完成記錄并保存。由于ID、ver均可以匹配上, 因此用戶A成功保存。但是,卻把用戶C插入的記錄覆蓋掉了。
樂觀鎖此時(shí)的失效,根本原因在于應(yīng)用所使用的主鍵ID管理策略, 正好與樂觀鎖存在極小程度上的不兼容。
兩者分開來看,都是沒問題的。組合到一起之后,大致看去好像也沒問題。 但是bug之所以成為bug,坑之所以能夠坑死人,正是由于其隱蔽性。
對(duì)此,也有一些意見提出來,使用時(shí)間戳作為版本號(hào)字段,就可以避免這個(gè)問題。 但是,時(shí)間戳的話,如果精度不夠,如毫秒級(jí)別,那么在高并發(fā),或者非常湊巧情況下, 仍有失效的可能。而如果使用高精度時(shí)間戳的話,成本又太高。
使用時(shí)間戳,可靠性并不比使用整型好。問題還是要回到使用嚴(yán)謹(jǐn)?shù)闹麈I成生策略上來。