5-事務、數(shù)據(jù)庫事務、事務隔離級別、鎖的簡單總結(jié)

轉(zhuǎn)載自:http://www.itdecent.cn/p/818d2b41c743


一、數(shù)據(jù)庫事務

1、事務是作為單個邏輯工作單元執(zhí)行的一系列操作??梢允且粭lSQL語句也可以是多條SQL語句。

2、事務具有四個特性

原子性(Atomicity):事務中的全部操作在數(shù)據(jù)庫中是不可分割的,要么全部完成,要么均不執(zhí)行。

一致性(Consistency):幾個并行執(zhí)行的事務,其執(zhí)行結(jié)果必須與按某一順序串行執(zhí)行的結(jié)果相一致。

隔離性(Isolation):事務的執(zhí)行不受其他事務的干擾,事務執(zhí)行的中間結(jié)果對其他事務必須是透明的。

持久性(Durability):對于任意已提交事務,系統(tǒng)必須保證該事務對數(shù)據(jù)庫的改變不被丟失,即使數(shù)據(jù)庫出現(xiàn)故障。

3、啟動事務:使用 API 函數(shù)和 Transact-SQL 語句,可以按顯式、自動提交或隱式的方式來啟動事務。

4、結(jié)束事務:您可以使用 COMMIT(成功) 或 ROLLBACK(失敗) 語句,或者通過 API 函數(shù)來結(jié)束事務。

5、創(chuàng)建事務的原則

盡可能使事務保持簡短很重要,當事務啟動后,數(shù)據(jù)庫管理系統(tǒng) (DBMS) 必須在事務結(jié)束之前保留很多資源、以保證事務的正確安全執(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ù)不同的隔離級別,可以導致不同的結(jié)果。不同事務隔離級別能夠解決的數(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隔離級別就已經(jīng)滿足了SQL 92標準的REPEATABLE READ隔離級別。?SQL 92推薦使用REPEATABLE READ以保證數(shù)據(jù)的讀一致性,不過用戶可以根據(jù)應用的需要選擇適合的隔離等級。

問題分析:

a、更新丟失:兩個事務都同時更新一行數(shù)據(jù),一個事務對數(shù)據(jù)的更新把另一個事務對數(shù)據(jù)的更新覆蓋了。這是因為系統(tǒng)沒有執(zhí)行任何的鎖操作,并發(fā)事務沒有被隔離開來。

第一類更新丟失:A事務撤銷時,把已經(jīng)提交的B事務的更新數(shù)據(jù)覆蓋了。這種錯誤可能造成很嚴重的問題,通常數(shù)據(jù)庫的實現(xiàn)是不允許發(fā)生這種情況。

第二類更新丟失:A事務覆蓋B事務已經(jīng)提交的數(shù)據(jù),造成B事務所做操作丟失。

b、臟讀(Dirty Read):一個事務讀取到了另一個事務未提交的數(shù)據(jù)操作結(jié)果。這是相當危險的,因為很可能所有的操作都被回滾。

c、不可重復讀(虛讀)(NonRepeatable Read):一個事務對同一行數(shù)據(jù)重復讀取兩次,但是卻得到了不同的結(jié)果。例如事務T1讀取某一數(shù)據(jù)后,事務T2對其做了修改,當事務T1再次讀該數(shù)據(jù)時得到與前一次不同的值。

d、幻讀(Phantom Read):事務在操作過程中進行兩次查詢,第二次查詢的結(jié)果包含了第一次查詢中未出現(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直到事務結(jié)束才釋放鎖。另說明,如果select語句中指定了HoldLock提示,則也要等到事務結(jié)束才釋放鎖。

三、鎖

1、事務使用鎖,防止其他用戶訪問一個還未完成的事務中的數(shù)據(jù)。對于多用戶系統(tǒng)來說,鎖機制是必須的。有多種類型的鎖,允許事務鎖定不同的資源。鎖就是保護指定的資源,不被其他事務操作。鎖定比較小的對象,例如鎖定行,雖然可以提高并發(fā)性,但是卻有較高的開支,因為如果鎖定許多行,那么需要占有更多的鎖。鎖定比較大的對象,例如鎖定表,會大大降低并發(fā)性,因為鎖定整個表就限制了其他事務訪問該表的其他部分,但是成本開支比較低,因為只需維護比較少的鎖。

2、鎖的特點:

a、鎖是保證并發(fā)控制的手段

b、鎖的類型,主要包括共享鎖和排它鎖;共享鎖允許其他事務繼續(xù)使用鎖定的資源,排它鎖只允許一個事務訪問數(shù)據(jù)

c、鎖的粒度,可以鎖定的資源包括行、頁、簇、表和數(shù)據(jù)庫

d、鎖的時間,鎖的時間應該包括兩種:一種是sql執(zhí)行完就釋放鎖,另一種是事務結(jié)束后釋放鎖。

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內(nèi)部使用的,不需要用戶干預。

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ù)庫的悲觀鎖調(diào)用:

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 內(nèi)部使用,如 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ù)已經(jīng)通過不包含 for update 子句的 Select SQL 加載進來,所謂數(shù)據(jù)庫加鎖也就無從談起。

樂觀鎖( Optimistic Locking )

相對悲觀鎖而言,樂觀鎖機制采取了更加寬松的加鎖機制。悲觀鎖大多數(shù)情況下依靠數(shù)據(jù)庫的鎖機制實現(xiàn),以保證操作最大程度的獨占性。但隨之而來的就是數(shù)據(jù)庫性能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。

如一個金融系統(tǒng),當某個操作員讀取用戶的數(shù)據(jù),并在讀出的用戶數(shù)據(jù)的基礎上進行修改時如更改用戶帳戶余額,如果采用悲觀鎖機制,也就意味著整個操作過程中(從操作員讀出數(shù)、開始修改直至提交修改結(jié)果的全過程,甚至還包括操作員中途去煮咖啡的時間),數(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ù)修改的結(jié)果覆蓋操作員 A 的操作結(jié)果的可能。

從上面的例子可以看出,樂觀鎖機制避免了長事務中的數(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)的可能性,并進行相應調(diào)整(比如可以將樂觀鎖策略放在數(shù)據(jù)庫存儲過程中實現(xiàn),對外只開放基于此存儲過程的數(shù)據(jù)更新途徑,而不是將數(shù)據(jù)庫表直接對外公開)。

Hibernate 在其數(shù)據(jù)訪問引擎中內(nèi)置了樂觀鎖實現(xiàn)。如果不用考慮外部系統(tǒng)對數(shù)據(jù)庫的更新操作,利用 Hibernate 提供的透明化樂觀鎖實現(xiàn),將大大提升我們的生產(chǎn)力。

Hibernate 中可以通過 class 描述符的 optimistic-lock 屬性結(jié)合 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ù)庫連接內(nèi),但是其使用簡單。

2、JTA事務的功能強大,事務可以跨越多個數(shù)據(jù)庫或多個DAO,使用也比較復雜;應用程序調(diào)用UserTransaction.begin()、 UserTransaction.commit() 和 serTransaction.rollback() 處理事務邊界。

3、容器事務,主要指的是J2EE應用服務器提供的事務管理,局限于EJB應用使用。

小禮物走一走,來簡書關注我

贊賞支持

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容