轉載自:http://www.reibang.com/p/818d2b41c743
一、數(shù)據(jù)庫事務
1阻课、事務是作為單個邏輯工作單元執(zhí)行的一系列操作叫挟。可以是一條SQL語句也可以是多條SQL語句限煞。
2抹恳、事務具有四個特性
原子性(Atomicity):事務中的全部操作在數(shù)據(jù)庫中是不可分割的,要么全部完成署驻,要么均不執(zhí)行奋献。
一致性(Consistency):幾個并行執(zhí)行的事務健霹,其執(zhí)行結果必須與按某一順序串行執(zhí)行的結果相一致。
隔離性(Isolation):事務的執(zhí)行不受其他事務的干擾瓶蚂,事務執(zhí)行的中間結果對其他事務必須是透明的糖埋。
持久性(Durability):對于任意已提交事務,系統(tǒng)必須保證該事務對數(shù)據(jù)庫的改變不被丟失窃这,即使數(shù)據(jù)庫出現(xiàn)故障瞳别。
3、啟動事務:使用 API 函數(shù)和 Transact-SQL 語句杭攻,可以按顯式祟敛、自動提交或隱式的方式來啟動事務。
4兆解、結束事務:您可以使用 COMMIT(成功) 或 ROLLBACK(失敼萏) 語句,或者通過 API 函數(shù)來結束事務锅睛。
5埠巨、創(chuàng)建事務的原則
盡可能使事務保持簡短很重要,當事務啟動后现拒,數(shù)據(jù)庫管理系統(tǒng) (DBMS) 必須在事務結束之前保留很多資源辣垒、以保證事務的正確安全執(zhí)行。
特別是在大量并發(fā)的系統(tǒng)中具练, 保持事務簡短以減少并發(fā) 資源鎖定爭奪乍构,將顯得更為重要甜无。
a扛点、事務處理,禁止與用戶交互岂丘,在事務開始前完成用戶輸入陵究。
b、在瀏覽數(shù)據(jù)時奥帘,盡量不要打開事務
c铜邮、盡可能使事務保持簡短。
d寨蹋、考慮為只讀查詢使用快照隔離松蒜,以減少阻塞。
e已旧、靈活地使用更低的事務隔離級別秸苗。
f、靈活地使用更低的游標并發(fā)選項运褪,例如開放式并發(fā)選項惊楼。
g玖瘸、在事務中盡量使訪問的數(shù)據(jù)量最小。
二檀咙、事務的隔離級別
1雅倒、事務常見的四種隔離級別
盡管數(shù)據(jù)庫為用戶提供了鎖的DML操作方式,但直接使用鎖管理是非常麻煩的弧可,因此數(shù)據(jù)庫為用戶提供了自動鎖機制蔑匣。只要用戶指定會話的事務隔離級別,數(shù)據(jù)庫就會分析事務中的SQL語句棕诵,然后自動為事務操作的數(shù)據(jù)資源添加上適合的鎖殖演。此外數(shù)據(jù)庫還會維護這些鎖,當一個資源上的鎖數(shù)目太多時年鸳,自動進行鎖升級以提高系統(tǒng)的運行性能趴久,而這一過程對用戶來說完全是透明的。
ANSI/ISO SQL 92標準定義了4個等級的事務隔離級別搔确,在相同數(shù)據(jù)環(huán)境下彼棍,使用相同的輸入,執(zhí)行相同的工作膳算,根據(jù)不同的隔離級別座硕,可以導致不同的結果。不同事務隔離級別能夠解決的數(shù)據(jù)并發(fā)問題的能力是不同的涕蜂,如下表:
事務的隔離級別和數(shù)據(jù)庫并發(fā)性是對立的华匾,兩者此增彼長。一般來說机隙,使用READ UNCOMMITED隔離級別的數(shù)據(jù)庫擁有最高的并發(fā)性和吞吐量蜘拉,而使用SERIALIZABLE隔離級別的數(shù)據(jù)庫并發(fā)性最低。
SQL 92定義READ UNCOMMITED主要是為了提供非阻塞讀的能力有鹿,Oracle雖然也支持READ UNCOMMITED旭旭,但它不支持臟讀,因為Oracle使用多版本機制徹底解決了在非阻塞讀時讀到臟數(shù)據(jù)的問題并保證讀的一致性葱跋,所以持寄,Oracle的READ COMMITTED隔離級別就已經滿足了SQL 92標準的REPEATABLE READ隔離級別。?SQL 92推薦使用REPEATABLE READ以保證數(shù)據(jù)的讀一致性娱俺,不過用戶可以根據(jù)應用的需要選擇適合的隔離等級稍味。
問題分析:
a、更新丟失:兩個事務都同時更新一行數(shù)據(jù)荠卷,一個事務對數(shù)據(jù)的更新把另一個事務對數(shù)據(jù)的更新覆蓋了模庐。這是因為系統(tǒng)沒有執(zhí)行任何的鎖操作,并發(fā)事務沒有被隔離開來僵朗。
第一類更新丟失:A事務撤銷時赖欣,把已經提交的B事務的更新數(shù)據(jù)覆蓋了屑彻。這種錯誤可能造成很嚴重的問題,通常數(shù)據(jù)庫的實現(xiàn)是不允許發(fā)生這種情況顶吮。
第二類更新丟失:A事務覆蓋B事務已經提交的數(shù)據(jù)社牲,造成B事務所做操作丟失。
b悴了、臟讀(Dirty Read):一個事務讀取到了另一個事務未提交的數(shù)據(jù)操作結果搏恤。這是相當危險的,因為很可能所有的操作都被回滾湃交。
c熟空、不可重復讀(虛讀)(NonRepeatable Read):一個事務對同一行數(shù)據(jù)重復讀取兩次,但是卻得到了不同的結果搞莺。例如事務T1讀取某一數(shù)據(jù)后息罗,事務T2對其做了修改,當事務T1再次讀該數(shù)據(jù)時得到與前一次不同的值才沧。
d迈喉、幻讀(Phantom Read):事務在操作過程中進行兩次查詢,第二次查詢的結果包含了第一次查詢中未出現(xiàn)的數(shù)據(jù)或者缺少了第一次查詢中出現(xiàn)的數(shù)據(jù)温圆,這是因為在兩次查詢過程中有另外一個事務插入數(shù)據(jù)造成的挨摸。
隔離級別分析:
a、讀未提交(Read Uncommitted):允許臟讀取岁歉,但不允許更新丟失得运。這種事務隔離控制可以通過“排他寫鎖”實現(xiàn)。
b锅移、讀提交(Read Committed):允許不可重復讀取熔掺,但不允許臟讀取。這種事務隔離控制可以通過“瞬間共享讀鎖”和“排他寫鎖”實現(xiàn)帆啃。
c瞬女、可重復讀(Repeatable Read):禁止不可重復讀取和臟讀取,但是有時可能出現(xiàn)幻影數(shù)據(jù)努潘。這種事務隔離控制可以通過“共享讀鎖”和“排他寫鎖”實現(xiàn)。
d坤学、可序列化(Serializable):提供嚴格的事務隔離疯坤。它要求事務序列化執(zhí)行,事務只能一個接著一個地執(zhí)行深浮,但不能并發(fā)執(zhí)行压怠。如果僅僅通過“行級鎖”是無法實現(xiàn)事務序列化的,必須通過其他機制保證新插入的數(shù)據(jù)不會被剛執(zhí)行查詢操作的事務訪問到飞苇。
2菌瘫、數(shù)據(jù)庫中蜗顽,通常默認隔離級別是“讀已提交”,在默認的事務隔離級別下:insert雨让,update雇盖,delete用的是排他鎖, 會等待事務完成。通常情況下可以把隔離級別設為Read Committed栖忠,它能避免臟讀崔挖,而且有較好的并發(fā)性能。盡管它會導致虛讀庵寞、幻讀等問題狸相,在可能出現(xiàn)這類問題的個別場合可以由應用程序釆用悲觀鎖或樂觀鎖來控制。
3捐川、SQL語句執(zhí)行之前脓鹃,一般可以通過自定義設置事務隔離級別,JDBC一般也支持修改會話級的事務隔離級別設置古沥。
4将谊、另外要提一點:SQL標準對事務隔離級別的規(guī)定,是按該級別不可能發(fā)生什么問題來確定的渐白;所以尊浓,不同的數(shù)據(jù)庫對事務隔離的級別實現(xiàn)方式不一樣,比如纯衍,鎖的類型栋齿、鎖的作用范圍與鎖的有效時間。
5襟诸、事務隔離級別的實現(xiàn)依據(jù)
a瓦堵、是否申請鎖和鎖類型
b、占用鎖的時間
c歌亲、鎖的粒度
例如:共享鎖的鎖定時間與事務的隔離級別有關菇用,如果隔離級別為Read
Committed的默認級別,只在讀取(select)的期間保持鎖定陷揪,即在查詢出數(shù)據(jù)以后就釋放了鎖惋鸥;如果隔離級別為更高的Serializable直到事務結束才釋放鎖。另說明悍缠,如果select語句中指定了HoldLock提示卦绣,則也要等到事務結束才釋放鎖。
三飞蚓、鎖
1滤港、事務使用鎖,防止其他用戶訪問一個還未完成的事務中的數(shù)據(jù)趴拧。對于多用戶系統(tǒng)來說溅漾,鎖機制是必須的山叮。有多種類型的鎖,允許事務鎖定不同的資源添履。鎖就是保護指定的資源屁倔,不被其他事務操作。鎖定比較小的對象缝龄,例如鎖定行汰现,雖然可以提高并發(fā)性,但是卻有較高的開支叔壤,因為如果鎖定許多行瞎饲,那么需要占有更多的鎖。鎖定比較大的對象炼绘,例如鎖定表嗅战,會大大降低并發(fā)性,因為鎖定整個表就限制了其他事務訪問該表的其他部分俺亮,但是成本開支比較低驮捍,因為只需維護比較少的鎖。
2脚曾、鎖的特點:
a东且、鎖是保證并發(fā)控制的手段
b、鎖的類型本讥,主要包括共享鎖和排它鎖珊泳;共享鎖允許其他事務繼續(xù)使用鎖定的資源,排它鎖只允許一個事務訪問數(shù)據(jù)
c拷沸、鎖的粒度色查,可以鎖定的資源包括行、頁撞芍、簇秧了、表和數(shù)據(jù)庫
d、鎖的時間序无,鎖的時間應該包括兩種:一種是sql執(zhí)行完就釋放鎖验毡,另一種是事務結束后釋放鎖。
3愉镰、數(shù)據(jù)庫鎖機制
數(shù)據(jù)庫通過鎖的機制解決并發(fā)訪問的問題米罚,雖然不同的數(shù)據(jù)庫在實現(xiàn)細節(jié)上存在差別,但原理基本上是一樣的丈探。
按鎖定的對象的不同,一般可以分為表鎖定和行鎖定拔莱,前者對整個表進行鎖定碗降,而后者對表中特定行進行鎖定隘竭。從并發(fā)事務鎖定的關系上看,可以分為共享鎖定和獨占鎖定讼渊。共享鎖定會防止獨占鎖定动看,但允許其他的共享鎖定。而獨占鎖定既防止其他的獨占鎖定爪幻,也防止其他的共享鎖定菱皆。
為了更改數(shù)據(jù),數(shù)據(jù)庫必須在進行更改的行上施加行獨占鎖定挨稿,INSERT仇轻、UPDATE、DELETE和SELECT FOR UPDATE語句都會隱式采用必要的行鎖定奶甘。
4篷店、InnoDB引擎的鎖機制
共享鎖(S):允許一個事務去讀一行,阻止其他事務獲得相同數(shù)據(jù)集的排他鎖臭家。
排他鎖(X):允許獲得排他鎖的事務更新數(shù)據(jù)疲陕,阻止其他事務取得相同數(shù)據(jù)集的共享讀鎖和排他寫鎖。
意向共享鎖(IS):事務打算給數(shù)據(jù)行加行共享鎖钉赁,事務在給一個數(shù)據(jù)行加共享鎖前必須先取得該表的IS鎖蹄殃。
意向排他鎖(IX):事務打算給數(shù)據(jù)行加行排他鎖,事務在給一個數(shù)據(jù)行加排他鎖前必須先取得該表的IX鎖你踩。
說明:
1)共享鎖和排他鎖都是行鎖诅岩,意向鎖都是表鎖,應用中我們只會使用到共享鎖和排他鎖姓蜂,意向鎖是mysql內部使用的按厘,不需要用戶干預。
2)對于UPDATE钱慢、DELETE和INSERT語句逮京,InnoDB會自動給涉及數(shù)據(jù)集加排他鎖;對于普通SELECT語句束莫,InnoDB不會加任何鎖懒棉,事務可以通過以下語句顯示給記錄集加共享鎖或排他鎖。
共享鎖(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
排他鎖(X):SELECT * FROM table_name WHERE ... FOR UPDATE
3)InnoDB行鎖是通過給索引上的索引項加鎖來實現(xiàn)的览绿,因此InnoDB這種行鎖實現(xiàn)特點意味著:只有通過索引條件檢索數(shù)據(jù)策严,InnoDB才使用行級鎖,否則饿敲,InnoDB將使用表鎖妻导!
5、程序使用鎖(并發(fā)控制類型分為兩大類:樂觀并發(fā)控制和悲觀并發(fā)控制)
悲觀鎖( Pessimistic Locking )
悲觀鎖,正如其名倔韭,它指的是對數(shù)據(jù)被外界(包括本系統(tǒng)當前的其他事務术浪,以及來自外部系統(tǒng)的事務處理)修改持保守態(tài)度,因此寿酌,在整個數(shù)據(jù)處理過程中胰苏,將數(shù)據(jù)處于鎖定狀態(tài)。悲觀鎖的實現(xiàn)醇疼,往往依靠數(shù)據(jù)庫提供的鎖機制(也只有數(shù)據(jù)庫層提供的鎖機制才能真正保證數(shù)據(jù)訪問的排他性硕并,否則,即使在本系統(tǒng)中實現(xiàn)了加鎖機制秧荆,也無法保證外部系統(tǒng)不會修改數(shù)據(jù))倔毙。
一個典型的依賴數(shù)據(jù)庫的悲觀鎖調用:
select * from account where name=”Erica” for update
這條 sql 語句鎖定了 account 表中所有符合檢索條件(name=”Erica”)的記錄。本次事務提交之前(事務提交時會釋放事務過程中的鎖)辰如,外界無法修改這些記錄普监。
Hibernate 的悲觀鎖,也是基于數(shù)據(jù)庫的鎖機制實現(xiàn)琉兜。
下面的代碼實現(xiàn)了對查詢記錄的加鎖:
String hqlStr ="from TUser as user where user.name='Erica'";
Query query = session.createQuery(hqlStr);
query.setLockMode("user", LockMode.UPGRADE); // 加鎖
List userList = query.list();// 執(zhí)行查詢凯正,獲取數(shù)據(jù)
query.setLockMode 對查詢語句中,特定別名所對應的記錄進行加鎖(我們?yōu)門User 類指定了一個別名 “user” )豌蟋,這里也就是對返回的所有 user 記錄進行加鎖廊散。
Hibernate 通過使用數(shù)據(jù)庫的 for update 子句實現(xiàn)了悲觀鎖機制。
Hibernate 的加鎖模式有:
LockMode.NONE :無鎖機制梧疲。
LockMode.WRITE :Hibernate 在 Insert 和 Update 記錄的時候會自動獲取允睹。
LockMode.READ :Hibernate 在讀取記錄的時候會自動獲取炕横。
以上這三種鎖機制一般由 Hibernate 內部使用实苞,如 Hibernate 為了保證 Update過程中對象不會被外界修改,會在 save 方法實現(xiàn)中自動為目標對象加上 WRITE 鎖鹿驼。
LockMode.UPGRADE :利用數(shù)據(jù)庫的 for update 子句加鎖该互。
LockMode. UPGRADE_NOWAIT :Oracle 的特定實現(xiàn)米者,利用 Oracle 的 for update nowait 子句實現(xiàn)加鎖。
上面這兩種鎖機制是我們在應用層較為常用的宇智,加鎖一般通過以下方法實現(xiàn):
Criteria.setLockMode
Query.setLockMode
Session.lock
注意蔓搞,只有在查詢開始之前(也就是 Hiberate 生成 SQL 之前)設定加鎖,才會真正通過數(shù)據(jù)庫的鎖機制進行加鎖處理随橘,否則喂分,數(shù)據(jù)已經通過不包含 for update 子句的 Select SQL 加載進來,所謂數(shù)據(jù)庫加鎖也就無從談起机蔗。
樂觀鎖( Optimistic Locking )
相對悲觀鎖而言蒲祈,樂觀鎖機制采取了更加寬松的加鎖機制甘萧。悲觀鎖大多數(shù)情況下依靠數(shù)據(jù)庫的鎖機制實現(xiàn),以保證操作最大程度的獨占性讳嘱。但隨之而來的就是數(shù)據(jù)庫性能的大量開銷幔嗦,特別是對長事務而言酿愧,這樣的開銷往往無法承受沥潭。
如一個金融系統(tǒng),當某個操作員讀取用戶的數(shù)據(jù)嬉挡,并在讀出的用戶數(shù)據(jù)的基礎上進行修改時如更改用戶帳戶余額钝鸽,如果采用悲觀鎖機制,也就意味著整個操作過程中(從操作員讀出數(shù)庞钢、開始修改直至提交修改結果的全過程拔恰,甚至還包括操作員中途去煮咖啡的時間),數(shù)據(jù)庫記錄始終處于加鎖狀態(tài)基括,可以想見颜懊,如果面對幾百上千個并發(fā),這樣的情況將導致怎樣的后果风皿。樂觀鎖機制在一定程度上解決了這個問題河爹。
樂觀鎖,大多是基于數(shù)據(jù)版本( Version
)記錄機制實現(xiàn)桐款。何謂數(shù)據(jù)版本咸这?即為數(shù)據(jù)增加一個版本標識,在基于數(shù)據(jù)庫表的版本解決方案中魔眨,一般是通過為數(shù)據(jù)庫表增加一個 “version”
字段來實現(xiàn)媳维。
讀取出數(shù)據(jù)時,將此版本號一同讀出遏暴,之后更新時侄刽,對此版本號加一。此時朋凉,將提交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫表對應記錄的當前版本信息進行比對州丹,如果提交的數(shù)據(jù)版本號大于數(shù)據(jù)庫表當前版本號,則予以更新侥啤,否則認為是過期數(shù)據(jù)当叭。
對于上面修改用戶帳戶信息的例子而言,假設數(shù)據(jù)庫中帳戶信息表中有一個version 字段盖灸,當前值為 1 蚁鳖;而當前帳戶余額字段(balance)為 $100 。
a> 操作員 A 此時將其讀出(version=1)赁炎,并從其帳戶余額中扣除 $50($100-$50)醉箕。
b> 在操作員 A 操作的過程中钾腺,操作員 B 也讀入此用戶信息(version=1),并從其帳戶余額中扣除 $20 ($100-$20)讥裤。
c> 操作員 A 完成了修改工作放棒,將數(shù)據(jù)版本號加一(version=2),連同帳戶扣除后余額(balance=$50)己英,提交至數(shù)據(jù)庫更新间螟,此時由于提交數(shù)據(jù)版本大于數(shù)據(jù)庫記錄當前版本,數(shù)據(jù)被更新损肛,數(shù)據(jù)庫記錄 version 更新為 2 厢破。
d> 操作員 B 完成了操作,也將版本號加一(version=2)試圖向數(shù)據(jù)庫提交數(shù)據(jù)(balance=$80)治拿,但此時比對數(shù)據(jù)庫記錄版本時發(fā)現(xiàn)摩泪,操作員 B 提交的數(shù)據(jù)版本號為 2 ,數(shù)據(jù)庫記錄當前版本也為 2 劫谅,不滿足“ 提交版本必須大于記錄當前版本才能執(zhí)行更新“ 的樂觀鎖策略见坑,因此,操作員 B 的提交被駁回捏检。這樣荞驴,就避免了操作員 B 用基于 version=1 的舊數(shù)據(jù)修改的結果覆蓋操作員 A 的操作結果的可能。
從上面的例子可以看出未檩,樂觀鎖機制避免了長事務中的數(shù)據(jù)庫加鎖開銷(操作員 A 和操作員 B 操作過程中戴尸,都沒有對數(shù)據(jù)庫數(shù)據(jù)加鎖),大大提升了大并發(fā)量下的系統(tǒng)整體性能表現(xiàn)冤狡。需要注意的是孙蒙,樂觀鎖機制往往基于系統(tǒng)中的數(shù)據(jù)存儲邏輯,因此也具備一定的局限性悲雳,如在上例中挎峦,由于樂觀鎖機制是在我們的系統(tǒng)中實現(xiàn),來自外部系統(tǒng)的用戶余額更新操作不受我們系統(tǒng)的控制合瓢,因此可能會造成臟數(shù)據(jù)被更新到數(shù)據(jù)庫中坦胶。在系統(tǒng)設計階段,我們應該充分考慮到這些情況出現(xiàn)的可能性晴楔,并進行相應調整(比如可以將樂觀鎖策略放在數(shù)據(jù)庫存儲過程中實現(xiàn)顿苇,對外只開放基于此存儲過程的數(shù)據(jù)更新途徑,而不是將數(shù)據(jù)庫表直接對外公開)税弃。
Hibernate 在其數(shù)據(jù)訪問引擎中內置了樂觀鎖實現(xiàn)纪岁。如果不用考慮外部系統(tǒng)對數(shù)據(jù)庫的更新操作,利用 Hibernate 提供的透明化樂觀鎖實現(xiàn)则果,將大大提升我們的生產力幔翰。
Hibernate 中可以通過 class 描述符的 optimistic-lock 屬性結合 version描述符指定漩氨。
現(xiàn)在,我們?yōu)橹笆纠械?TUser 加上樂觀鎖機制遗增。
1 . 首先為 TUser 的 class 描述符添加 optimistic-lock 屬性:
dynamic-insert="true" optimistic-lock="version">
……
optimistic-lock 屬性有如下可選取值:
none:無樂觀鎖
version:通過版本機制實現(xiàn)樂觀鎖
dirty:通過檢查發(fā)生變動過的屬性實現(xiàn)樂觀鎖
all:通過檢查所有屬性實現(xiàn)樂觀鎖
其中通過 version 實現(xiàn)的樂觀鎖機制是 Hibernate 官方推薦的樂觀鎖實現(xiàn)叫惊,同時也是 Hibernate 中,目前唯一在數(shù)據(jù)對象脫離 Session 發(fā)生修改的情況下依然有效的鎖機制做修。因此霍狰,一般情況下,我們都選擇 version 方式作為 Hibernate 樂觀鎖實現(xiàn)機制缓待。
2 . 添加一個 Version 屬性描述符
optimistic-lock="version">
……
注意 version 節(jié)點必須出現(xiàn)在 ID 節(jié)點之后蚓耽。這里我們聲明了一個 version 屬性,用于存放用戶的版本信息旋炒,保存在 TUser 表的version 字段中。
在代碼中執(zhí) tx.commit() 時可能拋出 StaleObjectStateException 異常签杈,指出版本檢查失敗瘫镇,說明當前事務正在試圖提交一個過期數(shù)據(jù)。通過捕捉這個異常答姥,我們就可以在樂觀鎖校驗失敗時進行相應處理铣除。
五、Java中的三種事務
差異:
1鹦付、JDBC事務控制的局限性在一個數(shù)據(jù)庫連接內尚粘,但是其使用簡單。
2敲长、JTA事務的功能強大郎嫁,事務可以跨越多個數(shù)據(jù)庫或多個DAO,使用也比較復雜祈噪;應用程序調用UserTransaction.begin()泽铛、 UserTransaction.commit() 和 serTransaction.rollback() 處理事務邊界。
3辑鲤、容器事務盔腔,主要指的是J2EE應用服務器提供的事務管理,局限于EJB應用使用月褥。
小禮物走一走弛随,來簡書關注我
贊賞支持