架构探险笔记7-事务管理简介
什么是事務
事務(Transaction)通俗的理解為一件事,要么做完,要么不做,不能做一半留一半。也就是說,事務必須是一個不可分割的整體,就像我們在化學課上學到的原子,原子是構成物質的最小單位。于是人們就歸納出第一個事務的特性:原子性(Atomicity)。
特別是在數據庫領域,事務是一個非常重要的概念,除了原子性以外,它還有一個及其重要的特性,那就是一致性(Consistency)。也就是說,執行完數據庫操作后,數據庫不會被破壞。打個比方,如果從A賬戶轉到B賬號,不可能A賬戶扣了錢,而B賬戶沒有加錢。
當我們編寫了一條update語句,提交到數據庫的那一剎那,有可能別人也提交了一條delete語句到數據庫中。也許我們都是對同一條記錄進行操作,可以想象,如果不稍加控制,就會有大麻煩。我們必須保證數據庫操作之間是“隔離”的(線程之間有時也要做到隔離)。彼此之間沒有任何干擾,這就是隔離性(Isolation)。要想真正做到操作之間完全沒有任何干擾是很難的,于是數據庫權威轉件就開始動腦筋了,“我們要指定一個規范,讓各個數據庫廠商都支持我們的規范!”,這個規范就是事務隔離級別(Transaction?Isolation?Level)。能定義出這么牛的規范真的挺不容易的,其實說白了就四個級別:
- READ_UNCOMMITED;? 讀未提交(臟讀、不可重復讀、虛讀都是有可能發生的)
- READ_COMMITED;? 讀已提交(避免臟讀,但是不可重復讀和虛讀是有可能發生的)
- REPEATABLE_READ;? 可重復讀(避免臟讀、不可重復讀,但是虛讀是有可能發生的)
- SERIALIZABLE? 事務串行執行(啥問題都沒有(避免臟讀、不可重復讀、虛讀的發生),但是效率低)
從上往下,級別越來越高,并發性越來越差,安全性越來越高。
當我們執行一條insert語句后,數據庫必須要保證有一條數據永久地存放在磁盤中,這也算事務的一條特性,它就是持久性(Durability)。
歸納一下,以上一共提到了事務的四條特性,把它們的英文單詞首字母合起來就是ACID,這就是傳說中的“事務ACID特性”。
真的是非常牛的特性。這四條特性是事務管理的基石,一定要透徹理解。此外還要明確,這4個家伙當中,誰才是“老大”?其實也就想清楚了:原子性是基礎,隔離性是手段,持久性是目的,真正的“老大”就是一致性。前三個特性都是為了一致性服務。
總結:
事務的4大特性
- 原子性
- 一致性
- 隔離性
- 持久性
?
事務的4大隔離級別
- read_uncommited
- read_commited
- repeatable_read
- Serialization
事務面臨的問題
ACID這4個特征當中,其實最難理解倒不是一致性,而是隔離性。因為它是保證一致性的重要手段,它是工具,使用它不能有半點差池。怪不得數據庫權威轉接都來研究所謂的事務隔離級別。其實,定義這四個級別就是為了解決數據在高并發下產生的問題:
- Dirty?Read(臟讀,一個事務,讀到了另一個事務未提交數據.)
- Unrepeatable?Read(不可重復讀,一個事務,讀到了另一個事務的提交數據(update).導致查詢結果不一致.)
- Phantom?Read(幻讀,一個事務,讀到了另一個事務的提交數據(insert).導致查詢結果不一致)
臟讀
(讀取未提交的update)
首先看看“臟讀”,數據怎么可能臟呢?其實臟數據也就是我們經常說的“垃圾數據”了。比如說,有兩個事物,它們在并發執行(也就是競爭)
余額應該是1500才對,在T5時間點的時候,事物A此時查詢余額為0元,這個數據就是臟數據(事物未提交,回滾造成臟讀),它是事物B造成的,明顯沒有進行隔離,滲透過來,亂套了。
所以臟讀這件事情是非常要不得的,一定要解決!讓事物之間隔離起來才是硬道理。
不可重復讀
(一事務update提交后,導致另一事務讀取兩次數據update前后不一樣)
事物A其實除了查詢兩次以外,其他什么事情都沒有做,結果錢就從1000變成0了。這就是重復讀了。其實這樣也是合理的,畢竟事務B提交了事務,數據庫將結果進行了持久化,所以事務A再次讀取時自然就發生了變化。
這種現象基本上是可以理解的,但是有些變態的場景下是不允許的。畢竟這種現象也是事務之間沒有隔離所造成的,但我們對于這種問題似乎可以忽略。
幻讀
(insert/delete導致兩次讀取總記錄數不一致)
最后一條是幻讀(虛讀),聽起來很奇幻。Phantom這個詞不就是“幽靈、鬼魂”嗎?其意義就是鬼在讀,不是人在讀,或者說搞不清為什么,它就變了。
銀行工作人員每次統計總存款時看到不一樣的結果。不過這確實也挺正常的,總存款增多了,肯定是這個時候有人在存錢。但是如果銀行系統真的這樣設計,那算是完了。這種情況下同樣也是由于事務沒有隔離造成的,但對于大多數應用系統而言,這似乎也是正常的。銀行里的系統要求非常嚴密,統計的時候,甚至會將所有的其他操作給隔離開,這種隔離級別的就算非常高了(估計要到serializable級別了)。
歸納一下以上提到了事務并發所引起的與讀取數據有關的問題,各用一句話來描述:
- 臟讀:事務A讀取了事務B未提交的數據,并在這個基礎上又做了其他操作。
- 不可重復讀:事務A讀取了事務B已提交的更改數據。
- 幻讀:事務A讀取了事務B已提交的新增數。
第一條(臟讀)是堅決抵制的,后兩條(不可重復讀、幻讀)在大多數情況下可不考慮。
這就是為什么必須要有事務隔離級別這個東西了,它就像一面墻一樣,隔離不同的事務。不同的事務隔離級別能處理的事務并發問題如表:
根據用戶的實際需求參考這張表,然后確定事務隔離級別,應該不再是一件難事了。
JDBC也提供了這四類事務隔離級別,但默認事務隔離級別對不同數據庫產品而言卻是不一樣的。我們熟知的mysql數據庫的默認事務隔離級別就是REPEATABLE_READ,Oracle、SQL?Server、DB2等都有自己的默認值。READ_MMITED已經可以解決大多數問題了,其他的就具體情況具體分析了。
若對其他數據庫的默認事務隔離級別不太清楚,可以用以下代碼來獲取:
DatabaseMetaData meta = DBUtil.getConnection().getMetaData(); int isolation = meta.getDefaultTransactionIsolation();執行結果:
通過mysql獲取/更改事務級別
--查看當前會話隔離級別 select @@tx_isolation;--查看系統當前隔離級別 select @@global.tx_isolation;--設置當前會話隔離級別 set session transaction isolatin level repeatable read;--設置系統當前隔離級別 set global transaction isolation level repeatable read;提示:在java.sql.Connection類中可查看所有的隔離級別。
我們知道JDBC只是連接java程序與數據庫的橋梁而已,那么數據庫又是怎樣隔離事務的呢?其實它就是“鎖”這個東西。當插入數據時,就鎖定表,這叫“鎖表”;當數據更新時,就鎖定行,這叫“鎖行”。
總結:
數據庫并發操作產生的問題
- 丟失更新(兩個事務同時update,可用排他鎖解決)
- 臟讀
- 不可重復讀
- 幻讀
數據庫的鎖機制
1、鎖的兩種分類方式
(1)從數據庫系統的角度來看,鎖分為以下三種類型:
mysql鎖機制分為表級鎖和行級鎖,行級鎖包括共享鎖、排他鎖和更新鎖。
?排他鎖,簡稱X鎖(Exclusive Lock)
??????獨占鎖鎖定的資源只允許進行鎖定操作的程序使用,其它任何對它的操作均不會被接受。執行數據更新命令,即INSERT、UPDATE 或DELETE 命令時,數據庫會自動使用排他鎖。但當對象上有其它鎖存在時,無法對其加排他鎖。排他鎖一直到事務結束才能被釋放。
?共享鎖,簡稱S鎖(Shared Lock)
??????共享鎖鎖定的資源可以被其它用戶讀取,但其它用戶不能修改它。在SELECT 命令執行時,數據庫通常會對對象進行共享鎖鎖定。通常加共享鎖的數據頁被讀取完畢后,共享鎖就會立即被釋放。
?更新鎖(Update Lock)
????? 更新鎖是為了防止死鎖而設立的。當數據庫準備更新數據時,它首先對數據對象作更新鎖鎖定,這樣數據將不能被修改,但可以讀取。等到數據庫確定要進行更新數據操作時,它會自動將更新鎖換為排他鎖。但當對象上有其它鎖存在時,無法對其作更新鎖鎖定。
????? 對于共享鎖大家可能很好理解,就是多個事務只能讀數據不能改數據,對于排他鎖大家的理解可能就有些差別,很多人以為排他鎖鎖住一行數據后,其他事務就不能讀取和修改該行數據,其實不是這樣的。排他鎖指的是一個事務在一行數據加上排他鎖后,其他事務不能再在其上加其他的鎖。mysql InnoDB引擎默認的修改數據語句,update,delete,insert都會自動給涉及到的數據加上排他鎖,select語句默認不會加任何鎖類型,如果加排他鎖可以使用select ...for update語句,加共享鎖可以使用select ... lock in share mode語句。所以加過排他鎖的數據行在其他事務種是不能修改數據的,也不能通過for update和lock in share mode鎖的方式查詢數據,但可以直接通過select ...from...查詢數據,因為普通查詢沒有任何鎖機制。
(2)從程序員的角度看,鎖分為以下兩種類型:
悲觀鎖(Pessimistic Lock)
??????悲觀鎖,正如其名,它指的是對數據被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度,因此在整個數據處理過程中,將數據處于鎖定狀態。悲觀鎖的實現,往往依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改數據)。
樂觀鎖(Optimistic Lock)
??????相對悲觀鎖而言,樂觀鎖機制采取了更加寬松的加鎖機制。悲觀鎖大多數情況下依靠數據庫的鎖機制實現,以保證操作最大程度的獨占性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。
????? 而樂觀鎖機制在一定程度上解決了這個問題。樂觀鎖,大多是基于數據版本( Version )記錄機制實現。何謂數據版本?即為數據增加一個版本標識,在基于數據庫表的版本解決方案中,一般是通過為數據庫表增加一個 “version” 字段來實現。讀取出數據時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,如果提交的數據版本號大于數據庫表當前版本號,則予以更新,否則認為是過期數據。
????? 常見實現方式:在數據表增加version字段,每次事務開始時將取出version字段值,而后在更新數據的同時version增加1(如: update xxx set data=#{data},version=version+1 where version=#{version} ),如沒有數據被更新,那么說明數據由其它的事務進行了更新,此時就可以判斷當前事務所操作的歷史快照數據。
隔離級別實現機制
x鎖 排他鎖 被加鎖的對象只能被持有鎖的事務讀取和修改,其他事務無法在該對象上加其他鎖,也不能讀取(不能加共享鎖讀取,正常不加鎖讀取是可以的)和修改該對象(修改對象會默認加X鎖)
s鎖 共享鎖 被加鎖的對象可以被持鎖事務讀取,但是不能被修改,其他事務也可以在上面再加s鎖。
在運用X鎖和S鎖對數據對象加鎖時,還需要約定一些規則 ,例如何時申請X鎖或S鎖、持鎖時間、何時釋放等。稱這些規則為封鎖協議(Locking Protocol)。對封鎖方式規定不同的規則,就形成了各種不同的封鎖協議。
- 一級封鎖協議 (對應 read uncommited)
一級封鎖協議是:事務T在修改數據R之前必須先對其加X鎖,直到事務結束才釋放。事務結束包括正常結束(COMMIT)和非正常結束(ROLLBACK)。
一級封鎖協議可以防止丟失修改,并保證事務T是可恢復的。使用一級封鎖協議可以解決丟失修改問題。
在一級封鎖協議中,如果僅僅是讀數據不對其進行修改,是不需要加鎖的,它不能保證可重復讀和不讀“臟”數據。
- 二級封鎖協議 (對應read commited)
二級封鎖協議是:一級封鎖協議加上事務T在讀取數據R之前必須先對其加S鎖,讀完后方可釋放S鎖
二級封鎖協議除防止了丟失修改,還可以進一步防止讀“臟”數據。但在二級封鎖協議中,由于讀完數據后即可釋放S鎖,所以它不能保證可重復讀。
- 三級封鎖協議 (對應reapetable read )
三級封鎖協議是:一級封鎖協議加上事務T在讀取數據R之前必須先對其加S鎖,直到事務結束才釋放。
三級封鎖協議除防止了丟失修改和不讀“臟”數據外,還進一步防止了不可重復讀。
鎖和隔離級別的關系
? ? ? ?實際開發中,直接操作數據庫中各種鎖的幾率相對比較少,更多的是利用數據庫提供的四個隔離級別,未提交讀、已提交讀、可重復讀、可序列化,那隔離級別和鎖是什么關系?通俗來說,隔離級別是鎖的一個整體打包解決方案,可以理解為隔離封裝了鎖。
Spring的事務傳播行為
?除了JDBC給我們提供的事務隔離級別這種解決方案以外,還有哪些解決方案可以完善事務管理功能呢?
不妨看看Spring的解決方案,其實它是對JDBC的補充或擴展。它提供了一個非常重要的功能 ---?事務傳播行為(Transaction?Propagation?Behavior)
Spring一共提供了7種事務傳播
- PROPAGATION_REQUIRED;
- PROPAGATION_REQUIRES_NEW;
- PROPAGATION_NESTED;
- PROPAGATION_NOT_SUPPORTS;
- PROPAGATION_SUPPORTS;
- PROPAGATION_NEVER;
- PROPAGATION_MANDATORY
我們可以這樣理解,首先要明確事務從哪里來傳播到哪里去?答案是從方法A傳播到方法B。Spring解決的只是方法之間的事務傳播,比如:
- 方法A有事務,方法B也有事務;
- 方法A有事務,方法B沒有事務;
- 方法A沒有事務,方法B有事務;
- 方法A沒有事務,方法B也沒有事務;
這樣就是4種了,還有3種特殊情況。下面做一個分析。
假設事務從方法A傳播到方法B,用戶需要面對方法B,問自己一個問題:方法A有事務嗎?
需要注意的是PROPAGATION_NESTED,不要被它的名字所欺騙--Nested(嵌套)。凡是在類似方法A調用方法B的時候,在方法B上使用了這種事務傳播行為,都是錯的。因為這是錯誤地以為PROPAGATION_NESTED就是為方法嵌套調用而準備的,其實默認的PROPAGATION_REQUIRED就可以做我們想要做的事情。
Spring給我們帶來了事務傳播行為,這確實是一個非常強大而又實用的功能。除此以外,它也提供了一些小的附加功能,比如:
推薦使用Spring的注解事務配置,而放棄XML式事務配置。因為注解實在是太優雅了。
在Spring配置文件中使用
<tx:annotation-driven />在需要事務的方法上使用:
@Transactional public void xxx(){ }可在Transaction注解中設置事務隔離級別、事務傳播行為、事務超時時間、是否只讀事務。
?
?
轉載于:https://www.cnblogs.com/aeolian/p/10031514.html
總結
以上是生活随笔為你收集整理的架构探险笔记7-事务管理简介的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: js高级笔记
- 下一篇: inode占满前因后果