數據庫事物併發機制

寫在前面:

事務是最小的邏輯執行單元,也是數據庫併發控制的基本單位,其執行的結果必須使數據庫從一種一致性狀態變到另外一種一致性狀態。數據庫

數據庫事物併發機制

事務具備四個重要特性,即原子性(Atomicity)、一致性(Consistency)、隔離性 (Isolation)和持久性 (Durability)。安全

本文首先敘述了數據庫中事務的本質及其四大特性(ACID)的內涵,而後重點介紹了事務隔離性的動機和內涵,並介紹了數據庫爲此所提供的事務隔離級別以及這些事務隔離級別能解決的事務併發問題。介於併發安全與併發效率的平衡,咱們通常不會一味地提升事務隔離級別來保證事務併發安全性,而是經過結合其餘機制(包括筆者提到的樂觀鎖和悲觀鎖機制)來解決數據庫事務併發問題。bash

一. 事務概述

通常而言,用戶的每次請求都對應一個業務邏輯方法,而且每一個業務邏輯方法每每具備邏輯上的原子性。此外,一個業務邏輯方法每每包括一系列數據庫原子訪問操做,而且這些數據庫原子訪問操做應該綁定成一個總體,即要麼所有執行,要麼所有不執行,經過這種方式咱們能夠保證數據庫的完整性。也就是說,事務是最小的邏輯執行單元,是數據庫維護數據一致性的基本單位。session

總的來講,事務是一個不可分割操做序列,也是數據庫併發控制的基本單位,其執行的結果必須使數據庫從一種一致性狀態變到另外一種一致性狀態多線程

原子性(Atomicity)併發

原子性是指事務包含的全部操做要麼所有成功,要麼所有失敗回滾。 所以,事務的操做若是成功就必需要徹底應用到數據庫,若是操做失敗則不會對數據庫有任何影響,也就是說,事務是應用中不可再分的最小邏輯執行體。性能

一致性(Consistency)ui

**一致性是指事務執行的結果必須使數據庫從一種一致性狀態變到另外一種一致性狀態,也就是說,一個事務執行以前和執行以後數據庫都必須處於一致性狀態。**拿轉帳來講,假設用戶A和用戶B二者的錢加起來一共是5000,那麼無論A和B之間如何轉帳,轉幾回帳,事務結束後兩個用戶的錢相加起來應該還得是5000,這就是事務的一致性。spa

隔離性 (Isolation) — 與事務併發直接相關hibernate

隔離性是指併發執行的事務之間不能相互影響。也就是說,對於任意兩個併發的事務 T1 和 T2,在事務 T1 看來,T2 要麼在 T1 開始以前就已經結束,要麼在 T1 結束以後纔開始,這樣每一個事務都感受不到有其餘事務在併發地執行。

持久性 (Durability)

**持久性是指一個事務一旦被提交了,那麼對數據庫中的數據的改變就是永久性的,即使是在數據庫系統遇到故障的狀況下也不會丟失提交事務的操做。**換句換說,事務一旦提交,對數據庫所作的任何改變都要記錄到永久的存儲器中(一般就是保存到物理數據庫)。

二. 事務隔離性的內涵

以上介紹完了事務的基本概念及其四大特性(簡稱ACID),如今重點來講明下事務的隔離性。**咱們知道,當多個線程都開啓事務操做數據庫中的數據時,數據庫系統要能進行隔離操做以保證各個線程獲取數據的準確性。**也就是說,事務的隔離性主要用於解決事務的併發安全問題,那麼事務的隔離性解決了哪些具體問題呢?

一、事務併發帶來的問題

髒讀

**髒讀是指在一個事務處理過程當中讀取了另外一個事務未提交的數據。**好比,當一個事務正在屢次修改某個數據,而當這個事務對數據的修改還未提交時,這時一個併發的事務來訪問該數據,就會形成數據的髒讀。看下面的例子:

公司發工資了,領導把5000元打到singo的帳號上,可是該事務並未提交,而singo正好去查看帳戶,發現工資已經到帳,是5000元整,很是高興。但是不幸的是,領導發現發給singo的工資金額不對,是2000元,因而迅速回滾了事務,修改金額後,將事務提交,最後singo實際的工資只有2000元,singo空歡喜一場。

**出現的上述狀況就是咱們所說的髒讀,即對於兩個併發的事務(事務A:領導給singo發工資、事務B:singo查詢工資帳戶),事務B讀取了事務A還沒有提交的數據。**特別地,當隔離級別設置爲 Read Committed 時,就能夠避免髒讀,可是仍可能會形成不可重複讀。特別地,大多數數據庫的默認級別就是Read committed,好比Sql Server , Oracle。

不可重複讀

**不可重複讀是指:對於數據庫中的某個數據,一個事務範圍內屢次查詢卻返回了不一樣的數據值,這是因爲在查詢間隔該數據被另外一個事務修改並提交了。**例如,事務 T1 在讀取某一數據,而事務 T2 立馬修改了這個數據而且提交事務,當事務T1再次讀取該數據就獲得了不一樣的結果,即發生了不可重複讀。**不可重複讀和髒讀的區別是,髒讀是某一事務讀取了另外一個事務未提交的髒數據,而不可重複讀則是讀取了前一事務提交的數據。**看下面的例子:

singo拿着工資卡去消費,系統讀取到卡里確實有2000元,而此時她的老婆也正好在網上轉帳,把singo工資卡的2000元轉到另外一帳戶,並在singo以前提交了事務,當singo扣款時,系統檢查到singo的工資卡已經沒有錢,扣款失敗,singo十分納悶,明明卡里有錢,爲什麼……

上述狀況就是咱們所說的不可重複讀,即兩個併發的事務(事務A:singo消費、事務B:singo的老婆網上轉帳),事務A事先讀取了數據,事務B緊接着更新了數據並提交了事務,而事務A再次讀取該數據時,數據已經發生了改變。當隔離級別設置爲Repeatable read時,能夠避免不可重複讀。這時,當singo拿着工資卡去消費時,一旦系統開始讀取工資卡信息(即事務開始),singo的老婆就不可能對該記錄進行修改,也就是singo的老婆不能在此時轉帳。特別地,MySQL的默認隔離級別就是 Repeatable read。

幻讀

**幻讀是事務非獨立執行時發生的一種現象,即在一個事務讀的過程當中,另一個事務可能插入了新數據記錄,影響了該事務讀的結果。**例如,事務 T1 對一個表中全部的行的某個數據項執行了從「1」修改成「2」的操做,這時事務T2又對這個表中插入了一行數據項,而這個數據項的數值仍是爲「1」而且提交給數據庫。這時,操做事務 T1 的用戶若是再查看剛剛修改的數據,會發現還有一行沒有修改,其實這行是從事務T2中添加的,就好像產生幻覺同樣,這就是發生了幻讀。**幻讀和不可重複讀都是讀取了另外一條已經提交的事務(這點與髒讀不一樣),所不一樣的是不可重複讀查詢的都是同一個數據項,而幻讀針對的是數據記錄插入/刪除問題,兩者關注的問題點不太相同。**看下面的例子:

singo的老婆工做在銀行部門,她時常經過銀行內部系統查看singo的信用卡消費記錄。有一天,她正在查詢到singo當月信用卡的總消費金額爲80元,而singo此時正好在外面胡吃海塞後在收銀臺買單,消費1000元,即新增了一條1000元的消費記錄並提交了事務,隨後singo的老婆將singo當月信用卡消費的明細打印到A4紙上,卻發現消費總額爲1080元,singo的老婆很詫異,覺得出現了幻覺,幻讀就這樣產生了。

當隔離級別設置爲Serializable(最高的事務隔離級別)時,不只能夠避免髒讀、不可重複讀,還能夠避免幻讀。但同時代價也花費最高,性能很低,通常不多使用,由於在該級別下併發事務將串行執行。

二、小結

總的來講,事務的隔離性主要用於解決事務併發安全問題。上面提到的髒讀、不可重複讀和幻讀三個典型問題都是在事務併發的前提下發生的,不一樣的是三者的問題關注點略有不一樣。

  • 髒讀關注的是事務讀取了另外一個事務未提交的數據;
  • 不可重複讀關注的是同一事務中對同一個數據項屢次讀取的結果互不相同;
  • 幻讀更側重於數據記錄的插入/刪除問題,好比同一事務中對符合同一條件的數據記錄的屢次查詢的結果互不相同。

更進一步地說,不可重複讀關注的是數據的更新帶來的問題,幻讀關注的是數據的增刪帶來的問題。

三. 數據庫的事務隔離級別

不一樣數據庫的事務隔離級別不盡相同。好比咱們在上一節提到,MySQL數據庫支持下面的四種隔離級別,而且默認爲 Repeatable read 級別;而在Oracle數據庫中,只支持Serializable 級別和 Read committed 這兩種級別,而且默認爲 Read committed 級別。MySQL數據庫爲咱們提供了四種隔離級別,分別爲:

  1. Serializable (串行化):最高級別,可避免髒讀、不可重複讀、幻讀的發生;
  2. Repeatable read (可重複讀):可避免髒讀、不可重複讀的發生;
  3. Read committed (讀已提交):可避免髒讀的發生;
  4. Read uncommitted (讀未提交):最低級別,任何狀況都沒法保證。

數據庫事物併發機制

數據庫事物併發機制

從上圖中能夠看出,**以上四種隔離級別中最高的是 Serializable級別,最低的是 Read uncommitted級別。**固然,隔離級別越高,事務併發就越安全,但執行效率也就越低。好比,Serializable 這樣的級別就是以鎖表的方式(相似於Java多線程中的鎖)保證併發事務的串行執行,但這時執行效率也降到了最低,因此,選用何種隔離級別實質上是一種併發安全與併發效率的平衡,應該根據實際狀況而定。特別地,在MySQL數據庫中,默認的事務隔離級別爲 Repeatable read(可重複讀),下面咱們看看如何在MySQL數據庫中操做事務的隔離級別。

MySQL默認事務隔離級別查看

在MySQL數據庫中,咱們能夠經過如下方式查看當前事務的隔離級別:

select @@tx_isolation;
複製代碼

數據庫事物併發機制

MySQL事務隔離級別修改

在MySQL數據庫中,咱們能夠分別經過如下兩種方式設置事務的隔離級別,分別爲:

set [glogal | session] transaction isolation level 隔離級別名稱;
複製代碼

set tx_isolation='隔離級別名稱';
複製代碼

數據庫事物併發機制

使用JDBC對設置數據庫事務的隔離級別

設置數據庫的隔離級別必定要是在開啓事務以前。特別地,使用JDBC對數據庫的事務設置隔離級別時,咱們應該在調用Connection對象的setAutoCommit(false)方法以前調用Connection對象的setTransactionIsolation(level)去設置當前連接的隔離級別以下所示:

數據庫事物併發機制

至於參數level,可使用Connection接口的字段,如如下代碼所示:

數據庫事物併發機制

特別地,經過這種方式設置事務隔離級別只對當前連接有效。**對於使用MySQL命令窗口而言,一個窗口就至關於一個連接,當前窗口設置的隔離級別只對當前窗口中的事務有效;**對於JDBC操做數據庫來講,一個Connection對象至關於一個連接,而對於Connection對象設置的隔離級別只對該Connection對象有效,與其餘連接Connection對象無關。

四. 數據庫併發控制

  • 也許你們已經據說過,**鎖分兩種,一個叫 悲觀鎖,一種稱之爲 樂觀鎖。**事實上,不管是悲觀鎖仍是樂觀鎖,都是人們定義出來的概念,是一種解決問題的思想。所以,不只僅在數據庫系統中有樂觀鎖和悲觀鎖的概念,像memcache、hibernate、tair等都有相似的概念。好比,**在線程併發處理中, Synchronized內置鎖 就是悲觀鎖的一種,也稱之爲 獨佔鎖,加了synchronized關鍵字的代碼基本上就只能以單線程的形式去執行了,它會致使其餘須要該資源的線程掛起,直到前面的線程執行完畢釋放所資源;**而 樂觀鎖是一種更高效的機制,它的原理就是每次不加鎖去執行某項操做,若是發生衝突則失敗並重試,直到成功爲止,其實本質上不算鎖,因此不少地方也稱之爲 自旋。
  • 在解決數據庫的事務併發訪問問題時,雖然將事務串形化能夠保證數據在多事務併發處理下不存在數據不一致的問題,但串行執行使得數據庫的處理性能大幅度地降低,經常是咱們接受不了的。因此,通常來講,咱們經常結合事務隔離級別和其它併發機制來保證事務的併發,以此來兼顧事務併發的效率與安全性。事實上,大多數數據庫的隔離級別都會設置爲 Read Committed(只能讀取其餘事務已提交的數據),而後由應用程序使用樂觀鎖/悲觀鎖機制來解決其餘事務併發問題,好比不可重複讀問題。特別地,樂觀併發控制(樂觀鎖)和悲觀併發控制(悲觀鎖)是併發控制主要採用的技術手段。
  • **樂觀鎖的理念是:假設不會發生併發衝突,只在提交操做時檢查是否違反數據完整性;而悲觀鎖的理念是假定會發生併發衝突,屏蔽一切可能違反數據完整性的操做。**針對於不一樣的業務場景,應該選用不一樣的併發控制方式。因此,不要把樂觀併發控制和悲觀併發控制狹義的理解爲DBMS中的概念,更不要把他們和數據中提供的鎖機制(行鎖、表鎖、排他鎖、共享鎖)混爲一談。須要指出的是,在DBMS中,悲觀鎖正是利用數據庫自己提供的鎖機制來實現的。

一、樂觀鎖

樂觀鎖,雖然名字中帶「鎖」,可是樂觀鎖並不鎖住任何東西,而是在提交事務時檢查這條記錄是否被其餘事務進行了修改:若是沒有,則提交;不然,進行回滾。相對於悲觀鎖,在對數據庫進行處理的時候,樂觀鎖並不會使用數據庫提供的鎖機制。若是併發的可能性並不大,那麼樂觀鎖定策略帶來的性能消耗是很是小的。樂觀鎖採用的實現方式通常是記錄數據版本。

二、悲觀鎖

**悲觀鎖,正如其名,它指的是對數據被外界修改持保守(悲觀)態度,所以,在整個數據處理過程當中,將數據處於鎖定狀態。悲觀鎖的實現每每依靠數據庫提供的鎖機制,也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,不然即便在本系統中實現了加鎖機制,也沒法保證外部系統不會修改數據。**悲觀併發控制主要用於數據爭用激烈的環境,以及發生併發衝突時使用鎖保護數據的成本要低於回滾事務的成本的環境中。和樂觀鎖相比,悲觀鎖則是一把真正的鎖了,它經過SQL語句「select for update」鎖住select出的那批數據,這時若是其餘事務來更新這批數據時會等待。

三、小結

悲觀鎖和樂觀鎖都是一種解決併發控制問題的思想。特別地,在數據庫併發控制方面,悲觀鎖與樂觀鎖有如下幾點區別:

  1. 思想:在事務併發環境中,樂觀鎖假設不會發生併發衝突,所以只在提交操做時檢查是否違反數據完整性;而悲觀鎖假定會發生併發衝突,會屏蔽一切可能違反數據完整性的操做。
  2. 實現:悲觀鎖是利用數據庫自己提供的鎖機制來實現的;而樂觀鎖則是經過記錄數據版本實現的;
  3. 應用場景:悲觀鎖主要用於數據爭用激烈的環境或者發生併發衝突時使用鎖保護數據的成本要低於回滾事務的成本的環境中;而樂觀鎖主要應用於併發可能性並不太大、數據競爭不激烈的環境中,這時樂觀鎖帶來的性能消耗是很是小的;
  4. 髒讀: 樂觀鎖不能解決髒讀問題,而悲觀鎖則能夠。

總的來講,悲觀鎖相對樂觀鎖更安全一些,可是開銷也更大,甚至可能出現數據庫死鎖的狀況,建議只在樂觀鎖沒法工做時才使用。

相關文章
相關標籤/搜索