數據庫事務和鎖

一、前言

此次咱們只談數據庫。以往遇到併發事務問題,總想着站在應用層面考慮問題,該如何實現悲觀鎖和樂觀鎖?但並不很清楚數據庫中底層的實現原理。找幾個晚上從新溫習了一下大學課堂上的《數據庫系統概覽》,借這篇文章記下來此次讀書的概要。mysql

二、四個特性 ACID

若是想要說明一個數據庫或者一個框架支持事務性操做,則必需要知足下面的四大特性。sql

  1. 原子性(Atomicity):原子性是指事務是一個不可再分割的工做單元,事務中的操做要麼都發生,要麼都不發生。
  2. 一致性(Consistency):一致性是指在事務開始以前和事務結束之後,數據庫的完整性約束沒有被破壞。這是說數據庫事務不能破壞關係數據的完整性以及業務邏輯上的一致性。
  3. 隔離性(Isolation):多個事務併發訪問時,事務之間是隔離的,一個事務不該該影響其它事務運行效果。
  4. 持久性(Durability):持久性是指事務的操做,一旦提交,對於數據庫中數據的改變是永久性的,即便數據庫發生故障也不能丟失已提交事務所完成的改變。

三、四種隔離級別

在數據庫事務的ACID四個屬性中,隔離性是一個最常放鬆的一個。能夠在數據操做過程當中利用數據庫的鎖機制或者多版本併發控制機制獲取更高的隔離等級。可是,隨着數據庫隔離級別的提升,數據的併發能力也會有所降低。因此,如何在併發性和隔離性之間作一個很好的權衡就成了一個相當重要的問題。數據庫

3.一、併發問題

實際開發過程當中,咱們絕大部分的事務都是有併發狀況。當多個事務併發運行,常常會操做相同的數據來完成各自的任務。在這種狀況下可能會致使如下的問題:併發

  1. 髒讀:指一個事務A正在訪問數據,而且對該數據進行了修改,可是這種修改尚未提交到數據庫中(也可能由於某些緣由Rollback了)。這時候另一個事務B也訪問這個數據,而後使用了這個被A修改的數據,那麼這個數據就是髒的,並非數據庫中真實的數據。這就被稱做髒讀。
  2. 不可重複讀: 事務 A 屢次讀取同一數據,事務 B 在事務A屢次讀取的過程當中,對數據做了更新並提交,致使事務A屢次讀取同一數據時,結果不一致。
  3. 幻讀:指一個事務A對一個表中的數據進行了修改,並且該修改涉及到表中全部的數據行;同時另外一個事務B也在修改表中的數據,該修改是向表中插入一行新數據。那麼通過這一番操做以後,操做事務A的用戶就會發現表中還有沒修改的數據行,就像發生了幻覺同樣。

不可重複讀的和幻讀很容易混淆,不可重複讀側重於修改,幻讀側重於新增或刪除。oracle

3.二、隔離級別

ANSI/ISO SQL定義的標準隔離級別有四種,從低到高依次爲:未提交讀(Read uncommitted) < 提交讀(Read committed) < 可重複讀(Repeatable reads) < 序列化(Serializable)。框架

  1. 未提交讀(Read Uncommitted):一個事務在執行過程當中,既能夠訪問其餘事務未提交的新插入的數據,又能夠訪問未提交的修改數據。若是一個事務已經開始寫數據,則另一個事務不容許同時進行寫操做,但容許其餘事務讀此行數據。此隔離級別可防止丟失更新。
  2. 已提交讀(Read Committed):一個事務在執行過程當中,既能夠訪問其餘事務成功提交的新插入的數據,又能夠訪問成功修改的數據。讀取數據的事務容許其餘事務繼續訪問該行數據,可是未提交的寫事務將會禁止其餘事務訪問該行。此隔離級別可有效防止髒讀。
  3. 可重複讀(Repeatable Read):一個事務在執行過程當中,能夠訪問其餘事務成功提交的新插入的數據,但不能夠訪問成功修改的數據。讀取數據的事務將會禁止寫事務(但容許讀事務),寫事務則禁止任何其餘事務。此隔離級別可有效防止不可重複讀和髒讀。
  4. 序列化(Serializable):提供嚴格的事務隔離。它要求事務序列化執行,事務只能一個接着一個地執行,不能併發執行。此隔離級別可有效防止髒讀、不可重複讀和幻讀。但這個級別可能致使大量的超時現象和鎖競爭,在實際應用中不多使用。

mysql:默認隔離級別是 Repeatable Read,會出現幻讀的問題。oracle/sql server:默認隔離級別是 Read Committed,會出現不可重複讀和幻讀的問題。spa

3.三、間隙鎖

產生幻讀的緣由是,行鎖只能鎖住行,可是新插入記錄這個動做,要更新的是記錄之間的「間隙」。所以,爲了解決幻讀問題,mysql InnoDB 只好引入新的鎖,也就是間隙鎖 (GapLock)。間隙鎖,鎖的就是兩個值之間的空隙。值得注意的是,間隙鎖只在隔離級別是Repeatable read 下才會生效。線程

  1. 行鎖(Record Lock):鎖直接加在索引記錄上面。
  2. 間隙鎖(Gap Lock):鎖加在不存在的空閒空間,能夠是兩個索引記錄之間,也多是第一個索引記錄以前或者最後一個索引以後的空間。
  3. Next-Key Lock:行鎖與間隙鎖組合起來用就叫作Next-Key Lock。

默認狀況下,InnoDB工做在"可重複讀"的隔離狀況下,而且以Next-Key Lock的方式對數據進行加鎖,這樣就能夠有效地防止"幻讀"的發生。Next-key Lock是行鎖與間隙鎖的組合,這樣,當InnoDB掃描索引記錄的時候,會首先對選中的索引記錄加上行鎖(Record Lock),再對索引記錄兩邊的間隙加上間隙鎖(Gap Lock)。若是一個間隙被事務T1加了鎖,其餘事務是不能在這個間隙插入記錄的。例以下面的sql,會對id取值範圍大於100的間隙加鎖。當增刪數據庫時若是id大於100則會阻塞住,若是小於100則不會有影響。code

select * from  emp where id > 100 for update;

四、鎖

鎖是數據庫系統區別於文件系統的一個關鍵特性,鎖機制用於管理對共享資源的併發訪問。粗略的分有行級鎖、表級鎖和頁級鎖,行級鎖再分共享鎖和排他鎖等。要記住悲觀鎖和樂觀鎖是兩個抽象的概念,並不是實際數據庫中的鎖。server

image.png

4.一、共享鎖

共享鎖(S鎖):容許一個事務去讀一行,會阻止其餘事務獲取相同數據集的排他鎖(讀取數據的時候不容許修改),也被稱爲讀鎖。當沒有其餘線程對查詢結果集中的任何一行使用排他鎖時,能夠成功申請共享鎖,不然會被阻塞。其餘線程也能夠讀取使用了共享鎖的表,並且這些線程讀取的是同一個版本的數據。

mysql的共享鎖支持行級鎖,在查詢語句後面增長LOCK IN SHARE MODE,mysql會對查詢結果中的每行都加共享鎖。

SELECT ... LOCK IN SHARE MODE;

oracle的共享鎖不支持行級鎖,只能是表級鎖。

LOCK TABLE <表名>[,<表名>]... IN SHARE MODE [NOWAIT]

有一些誤區須要記住,單純的查詢SELECT不是共享鎖,其實是沒有上任何鎖。而 SELECT ... FOR UPDATE 是排他鎖。

4.二、排他鎖

排他鎖(X鎖):容許獲取排他鎖去作更新操做,阻止其餘事務獲取相同數據的共享鎖和排他鎖(一個事務修改數據的時候,阻止其餘事務對相同數據集作更新或者查詢操做),也被稱爲寫鎖。

排他鎖的使用方式比較一致,新增、更新和刪除等DML操做語句會自動加上排他鎖。並且SELECT ... FOR UPDATE 也是排他鎖。

4.三、意向鎖

爲了容許行鎖和表鎖共存,實現多粒度鎖機制,InnoDB還有兩種內部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖:

意向共享鎖(IS):事務打算給數據行加共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的IS鎖。

意向排他鎖(IX):事務打算給數據行加排他鎖,事務再給一個數據行加排他鎖前必須先取得該表的IX鎖。

意向鎖是數據庫隱式幫咱們作了,咱們不須要操心。

4.四、悲觀鎖和樂觀鎖(抽象)

悲觀鎖:是從數據庫層面加鎖。老是假設最壞的狀況,每次去拿數據的時候都認爲別人會修改,因此每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它釋放鎖。

樂觀鎖:老是假設最好的狀況,每次去拿數據的時候都認爲別人不會修改,因此不會上鎖,可是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據。

那麼在數據庫層面上,咱們利用鎖來實現事務的,不管是共享鎖仍是排他鎖,都是悲觀鎖。而樂觀鎖是經過業務來實現,並不會利用到數據庫的鎖。

相關文章
相關標籤/搜索