這篇文章詳細(xì)介紹一下數(shù)據(jù)庫事務(wù)與鎖的相關(guān)知識。主要是一些概念性的東西看起來可能比較乏味,但作為一名合格的程序員來說,你應(yīng)該掌握也必須掌握。這些理論知識好比是一個人的內(nèi)功,我們平時敲代碼是外功,只有內(nèi)外兼修,相互促進(jìn),才能達(dá)到武林高手的境界。好了廢話不多說,下面開始。
數(shù)據(jù)庫事務(wù)
事務(wù)的邊界
事務(wù)的開始邊界(begin)?
事務(wù)的結(jié)束邊界(commit):提交事務(wù),永久保存被事務(wù)更新后的數(shù)據(jù)庫狀態(tài)。?
事務(wù)的異常結(jié)束邊界(rollback):撤銷事務(wù),使數(shù)據(jù)庫退回到執(zhí)行事務(wù)前的初始狀態(tài)。
每啟動一個MySQL.exe程序,就會得到一個單獨(dú)的數(shù)據(jù)庫連接。每個數(shù)據(jù)庫連接都有一個全局變量autocommit,表示當(dāng)前的事務(wù)模式,它有兩個值可選:
-
0:表示手工提交模式
-
1:表示自動提交模式,默認(rèn)值
我們可以查看和修改這個值。
數(shù)據(jù)庫事務(wù)的4個特性(ACID):
-
原子性(Atomicity):事務(wù)是一個原子操作單元,其對數(shù)據(jù)的修改,要么全部執(zhí)行,要么全都不執(zhí)行;
-
一致性(Consistent):在事務(wù)開始和完成時,數(shù)據(jù)都必須保持一致狀態(tài);
-
隔離性(Isolation):數(shù)據(jù)庫系統(tǒng)提供一定的隔離機(jī)制,保證事務(wù)在不受外部并發(fā)操作影響的“獨(dú)立”環(huán)境執(zhí)行;
-
持久性(Durable):事務(wù)完成之后,它對于數(shù)據(jù)的修改是永久性的,即使出現(xiàn)系統(tǒng)故障也能夠保持。
事務(wù)隔離級別
數(shù)據(jù)庫事務(wù)隔離級別,只是針對一個事務(wù)能不能讀取其它事務(wù)的中間結(jié)果。
-
Read Uncommitted (讀取未提交內(nèi)容)?
在該隔離級別,所有事務(wù)都可以看到其他未提交事務(wù)的執(zhí)行結(jié)果。本隔離級別很少用于實(shí)際應(yīng)用,因?yàn)樗男阅芤膊槐绕渌墑e好多少。讀取未提交的數(shù)據(jù),也被稱之為臟讀( Dirty Read )。 -
Read Committed (讀取提交內(nèi)容)?
這是大多數(shù)數(shù)據(jù)庫系統(tǒng)的默認(rèn)隔離級別(但不是 MySQL 默認(rèn)的)。它滿足了隔離的簡單定義:一個事務(wù)只能看見已經(jīng)提交事務(wù)所做的改變。這種隔離級別 也支持所謂的不可重復(fù)讀( Nonrepeatable Read ),因?yàn)橥皇聞?wù)的其他實(shí)例在該實(shí)例處理其間可能會有新的 commit ,所以同一 select 可能返回不同結(jié)果。 -
Repeatable Read (可重讀)?
這是 MySQL 的默認(rèn)事務(wù)隔離級別,它確保同一事務(wù)的多個實(shí)例在并發(fā)讀取數(shù)據(jù)時,會看到同樣的數(shù)據(jù)行。不過理論上,這會導(dǎo)致另一個棘手的問題:幻讀 ( Phantom Read )。簡單的說,幻讀指當(dāng)用戶讀取某一范圍的數(shù)據(jù)行時,另一個事務(wù)又在該范圍內(nèi)插入了新行,當(dāng)用戶再讀取該范圍的數(shù)據(jù)行時,會發(fā)現(xiàn)有新的 ” 幻影 ” 行。 InnoDB和 Falcon 存儲引擎通過多版本并發(fā)控制( MVCC , Multiversion Concurrency Control )機(jī)制解決了該問題。 -
Serializable (可串行化)?
這是最高的隔離級別,它通過強(qiáng)制事務(wù)排序,使之不可能相互沖突,從而解決幻讀問題。簡言之,它是在每個讀的數(shù)據(jù)行上加上共享鎖。在這個級別,可能導(dǎo)致大量的超時現(xiàn)象和鎖競爭。
隔離級別越高,越能保證數(shù)據(jù)的完整性和一致性,但是對并發(fā)性能的影響也越大。?
對于多數(shù)應(yīng)用程序,可以有效考慮把數(shù)據(jù)庫系統(tǒng)的隔離級別設(shè)為Read Committed,它能夠避免臟讀,而且具有較好的并發(fā)性能。盡管它會導(dǎo)致不可重復(fù)讀、虛度和第二類丟失更新這些并發(fā)問題,在可能出現(xiàn)這類問題的個別場合,可以由應(yīng)用程序采用悲觀鎖和樂觀鎖來控制。
事務(wù)的傳播性
-
PROPAGATION_REQUIRED?
加入當(dāng)前正要執(zhí)行的事務(wù),如果當(dāng)前事務(wù)不存在,那么就起一個新的事務(wù)。Spring 操作數(shù)據(jù)庫默認(rèn)的事務(wù)傳播行為就是 propagation_required 。 -
PROPAGATION_SUPPORTS?
如果當(dāng)前在事務(wù)中,即以事務(wù)的形式運(yùn)行,如果當(dāng)前不再一個事務(wù)中,那么就以非事務(wù)的形式運(yùn)行. -
PROPAGATION_MANDATORY?
必須在一個事務(wù)中運(yùn)行。也就是說,他只能被一個父事務(wù)調(diào)用。否則,他就要拋出異常。 -
PROPAGATION_REQUIRES_NEW?
掛起當(dāng)前事務(wù),另起一個新的事務(wù)。 -
PROPAGATION_NOT_SUPPORTED?
當(dāng)前不支持事務(wù)。如果在事務(wù)中,會掛起當(dāng)前事務(wù),自己以非事務(wù)的行為運(yùn)行。
-
PROPAGATION_NEVER?
不能在事務(wù)中運(yùn)行,如果在事務(wù)中運(yùn)行就會拋出異常。 -
PROPAGATION_NESTED?
嵌套的事務(wù)依賴父事務(wù),父事務(wù)提交,它跟著提交,父事務(wù)回滾,它跟著回滾。
行級鎖
Mysql中三種類型的鎖:
行級:引擎 INNODB , 單獨(dú)的一行記錄加鎖?
頁級:引擎 BDB,一次鎖定相鄰的一組記錄。?
表級:引擎 MyISAM , 理解為鎖住整個表,可以同時讀,寫不行。?
三種鎖的特性可大致歸納如下:?
1) 表級鎖:開銷小,加鎖快;不會出現(xiàn)死鎖;鎖定粒度大,發(fā)生鎖沖突的概率最高,并發(fā)度最低。?
2) 行級鎖:開銷大,加鎖慢;會出現(xiàn)死鎖;鎖定粒度最小,發(fā)生鎖沖突的概率最低,并發(fā)度也最高。?
3) 頁面鎖:開銷和加鎖時間界于表鎖和行鎖之間;會出現(xiàn)死鎖;鎖定粒度界于表鎖和行鎖之間,并發(fā)度一般。
我們這里主要談?wù)摰氖切屑夋i,一般的在秒殺系統(tǒng)中我們會對商品庫存使用行級鎖,因?yàn)槊霘⒌臅r候庫存是一個很重要的數(shù)據(jù),我們在創(chuàng)建數(shù)據(jù)庫的表時可能會出現(xiàn)下面這樣的設(shè)置:
ENGINE?=?InnoDB?AUTO_INCREMENT=10?DEFAULT?CHARACTER?SET?=?utf8?comment='用戶表
將引擎設(shè)置為InnoDB,InnnoDB與其他引擎的不同:一是支持事務(wù)(TRANCSACTION),二是采用了行級鎖。
InnoDB中兩種模式的行級鎖:
1)共享鎖:允許一個事務(wù)去讀一行,阻止其他事務(wù)獲得相同數(shù)據(jù)集的排他鎖。?
( Select * from table_name where ……lock in share mode)?
2)排他鎖:允許獲得排他鎖的事務(wù)更新數(shù)據(jù),阻止其他事務(wù)取得相同數(shù)據(jù)集的共享讀鎖和 排他寫鎖。(select * from table_name where…..for update)
為了允許行鎖和表鎖共存,實(shí)現(xiàn)多粒度鎖機(jī)制;同時還有兩種內(nèi)部使用的意向鎖(都是表鎖),分別為意向共享鎖和意向排他鎖。
-
意向共享鎖(IS):事務(wù)打算給數(shù)據(jù)行加行共享鎖,事務(wù)在給一個數(shù)據(jù)行加共享鎖前必須先取得該表的IS鎖。
-
意向排他鎖(IX):事務(wù)打算給數(shù)據(jù)行加行排他鎖,事務(wù)在給一個數(shù)據(jù)行加排他鎖前必須先取得該表的IX鎖。
注意:InnoDB行鎖是通過給索引上的索引項(xiàng)加鎖來實(shí)現(xiàn)的,這一點(diǎn)與Oracle不同,后者是通過在數(shù)據(jù)塊中對相應(yīng)數(shù)據(jù)行加鎖來實(shí)現(xiàn)的。InnoDB這種行鎖實(shí)現(xiàn)特點(diǎn)意味著:只有通過索引條件檢索數(shù)據(jù),InnoDB才使用行級鎖,否則,InnoDB將使用表鎖!MySQL的行鎖是針對索引加的鎖,不是針對記錄加的鎖,所以雖然是訪問不同行的記錄,但是如果是使用相同的索引鍵,是會出現(xiàn)鎖沖突的。應(yīng)用設(shè)計(jì)的時候要注意這一點(diǎn)。
行級鎖的優(yōu)缺點(diǎn)
行級鎖定的優(yōu)點(diǎn):
-
當(dāng)在許多線程中訪問不同的行時只存在少量鎖定沖突。
-
回滾時只有少量的更改。
-
可以長時間鎖定單一的行。
行級鎖定的缺點(diǎn):
-
比頁級或表級鎖定占用更多的內(nèi)存。
-
當(dāng)在表的大部分?jǐn)?shù)據(jù)上使用時,比頁級或表級鎖定速度慢,因?yàn)槟惚仨毇@取更多的鎖。如果你在大部分?jǐn)?shù)據(jù)上經(jīng)常進(jìn)行GROUP BY操作或
者必須經(jīng)常掃描整個表,比其它鎖定明顯慢很多。
hibernate中通過行級鎖實(shí)現(xiàn)的悲觀鎖。
一些例子:
假設(shè)有個表單products ,里面有id跟name二個欄位,id是主鍵。?
1: 明確指定主鍵,并且有此條記錄,執(zhí)行row lock。若查無此記錄,無lock。
SELECT?*?FROM?products?WHERE?id='3'?FOR?UPDATE;SELECT?*?FROM?products?WHERE?id='3'?and?name="cat"?FOR?UPDATE;
2: 無主鍵,執(zhí)行table lock。
SELECT?*?FROM?products?WHERE?name='Mouse'?FOR?UPDATE;
3: 主鍵不明確,table lock。
SELECT?*?FROM?products?WHERE?id'3'?FOR?UPDATE;
注意: FOR UPDATE僅適用于InnoDB,且必須在事務(wù)塊(BEGIN/COMMIT)中才能生效。此外,如果A與B都對表id進(jìn)行查詢但查詢不到記錄,則A與B在查詢上不會進(jìn)行row鎖,但A與B都會獲取排它鎖,此時A再插入一條記錄的話則會因?yàn)锽已經(jīng)有鎖而處于等待中,此時B再插入一條同樣的數(shù)據(jù)則會拋出Deadlock found when trying to get lock; try restarting transaction。然后釋放鎖,此時A就獲得了鎖而插入成功。
以上就是MySQL中的事務(wù)與鎖的內(nèi)容,更多相關(guān)內(nèi)容請關(guān)注PHP中文網(wǎng)(www.php.cn)!