資料庫併發訪問、事務與鎖、髒讀、不可重複讀、幻讀
資料庫併發訪問、事務與鎖的關係
一、事務
I : 事務的定義:
首先,讓我們瞭解下什麼是事務?事務是作為單個邏輯單元工作執行的一系列操作。可以是一條 sql語句,也可以是多條 sql 語句 ( 這是它的描述性定義 ) 。
II 事務的特性:
1 )原子性 (Atomic) :指整個資料庫事務是不可分割的工作單位。
2 )一致性 (Consistency) :指資料庫事務不能破壞關係資料的完整性以及業務邏輯的一致性。
3 ) 隔離性 (Isolation) :指的是在併發環境中,當不同的事務同時操作相同的資料時,每個事務都有各自的完整資料空間。
4 ) 永續性 (Durability) :指的是隻要事務成功結束,它對資料庫所做的更改就必須永久儲存下來。
資料庫採用日誌來保證事務的原子性,一致性和永續性,日誌記錄了事務對資料庫所做的更新,如果某個事務在執行過程中發生錯誤,就可以根據日誌,撤銷事務對資料庫已做的更改,使資料庫退回到執行事務前的初始狀態。
資料庫使用鎖機制來保證事務的隔離性,擁有各自獨立的空間。
事務具有這四個特徵是什麼意思呢?是不是說作為一個事務必須具有這四個特性才成為事務或者就不是事務呢?本人是這樣認為的,事務必須有原子性、一致性和永續性三個特徵,而隔離性則根據實際的需要確定隔離的程度和隔離的級別(正是基於此才引出後面的事務的隔離級別)。
III . 事務的隔離級別
1 、資料庫事務的隔離級別:四種
隔離級別 |
髒讀( Dirty Read ) |
寫覆蓋(Write Cover) |
不可重複讀(NonRepeatable Read ) |
幻讀(Phantom Read ) |
讀未提交( Read uncommitted ) |
可能 |
可能 |
可能 |
可能 |
讀已提交( Read committed ) |
不可能 |
可能 |
可能 |
可能 |
可重複讀(Repeatable read) |
不可能 |
|
不可能 |
可能 |
可序列化(Serializable ) |
不可能 |
|
不可能 |
不可能 |
那麼事務的隔離級別與鎖有什麼關係呢?本人認為事務的隔離級別是通過鎖的機制實現的,事務的隔離級別是資料庫開發商根據業務邏輯的實際需要定義的一組鎖的使用策略。當我們將資料庫的隔離級別定義為某一級別後如仍不能滿足要求,我們可以自定義 sql 的鎖來覆蓋事務隔離級別預設的鎖機制。
讀取未提交( Read Uncommitted)
這是最低的事務隔離級別,讀事務不會阻塞讀事務和寫事務,寫事務也不會阻塞讀事務,但是會阻塞寫事務。這樣造成的一個結果就是當一個寫事務沒有提交的時候,讀事務照樣可以讀取,那麼造成了髒讀的現象。
讀取已提交 (Read Committed)
採用此種隔離界別的時候,寫事務就會阻塞讀事務和寫事務,但是讀事務不會阻塞讀事務和寫事務,這樣因為寫事務會阻塞讀取事務,那麼從而讀取事務就不能讀到髒資料 ,但是因為讀事務不會阻塞其它的事務,這樣還是會造成不可重複讀的問題
可重複讀( Repeatable Read)
採用此種隔離級別,讀事務會阻塞寫事務 ,但是讀事務不會阻塞讀事務,但是寫事務會阻塞寫事務和讀事務 。因為讀事務阻塞了寫事務,這樣以來就不會造成不可重複讀的問題,但是這樣還是不能避免幻影讀問題。
序列化( serializable)
此種隔離級別是最嚴格的隔離級別,如果設定成這個級別,那麼就不會出現以上所有的問題(髒讀,不可重複讀,幻影讀)。但是這樣以來會極大的影響到我們系統的效能,因此我們應該避免設定成為這種隔離級別。
在實踐中,我們一般採用讀取已提交或者更低的事務隔離級別,配合各種併發訪問控制策略來達到併發事務控制的目的。
二、鎖
MySQL 的 InnoDB 有兩種模式的行鎖:
1 )共享鎖:允許一個事務去讀一行,阻止其他事務獲得相同資料集的排他鎖。
( Select * from table_name where ......lock in share mode)
2 )排他鎖:允許獲得排他鎖的事務更新資料,阻止其他事務取得相同資料集的共享讀鎖和 排他寫鎖。 (select * from table_name where.....for update)
為了允許行鎖和表鎖共存,實現多粒度鎖機制;同時還有兩種內部使用的意向鎖(都是表鎖),分別為意向共享鎖和意向排他鎖。
InnoDB 行鎖是通過給索引項加鎖來實現的,即只有通過索引條件檢索資料, InnoDB 才使用行級鎖,否則將使用表鎖!
三、併發控制
當許多人試圖同時修改資料庫中的資料時,必須實現一個控制系統,使一個人所做的修改不會對其他人產生負面影響,這成為併發控制,併發控制有兩種型別:
樂觀併發控制和悲觀併發控制。
樂觀併發控制:
樂觀鎖:
基本思想:每次提交一個事務更新時,我們先看看要修改的東西從上次讀取以後有沒有被修改過,如果修改過,那麼更新就會失敗。
樂觀鎖其實並不會鎖定任何記錄,所以如果我們資料庫的事務隔離級別設定為讀取已提交或者更低的隔離界別,那麼是不能避免不可重複讀問題的(因為此時讀事務不會阻塞其它事務),所以採用樂觀鎖的時候,系統應該要容許不可重複讀問題的出現。
樂觀鎖的實現策略
版本 (Version) 欄位:在我們的實體中增加一個版本控制欄位,每次事務更新後就將版本欄位的值加1.
時間戳 (timestamps): 採取這種策略後,當每次要提交更新的時候就會將系統當前時間和修改時間。
悲觀鎖也就是上面講到的我們認為的通常意義上的鎖 。
以上有些是本人的一些愚見!大家感覺有什麼不對的,歡迎指出願和大家一起交流共同進步!
資料庫的事務隔離級別(TRANSACTION ISOLATION LEVEL)是一個數據庫上很基本的一個概念。為什麼會有事務隔離級別,SQL Server上實現了哪些事務隔離級別?事務隔離級別的前提是一個多使用者、多程序、多執行緒的併發系統,在這個系統中為了保證資料的一致性和完整性,我們引入了事務隔離級別這個概念,對一個單使用者、單執行緒的應用來說則不存在這個問題。
首先,我們來看一下高併發的系統中會存在哪些問題,為了便於理解我們以張三在招商銀行的賬號和存款為例。
一、準備工作:
1. 建立一個銀行賬號Table(只是為了說明問題,不考慮表的設計正規化)
CREATE TABLE dbo.BankAccount
(
BankAccountId CHAR(16) NOT NULL, -- 銀行賬號
UserName NVARCHAR(32) NOT NULL, -- 使用者
Balance DECIMAL(19, 2) NOT NULL, -- 餘額
LastUpdate SMALLDATETIME NOT NULL
)
GO
2. 準備資料
INSERT INTO dbo.BankAccount
VALUES ('9555500100071120', N'張三', 10000.00, GETDATE()) -- 北京分行賬號
INSERT INTO dbo.BankAccount
VALUES ('9555507551227787', N'張三', 20000.00, GETDATE()) -- 深圳分行賬號
GO
3. 檢視資料
SELECT * FROM dbo.BankAccount
二、應用場景
假設張三在招商銀行開設了兩個賬號,一個是招商銀行北京分行,一個是招商銀行深圳分行,兩個賬號的餘額分別是10,000和20,000。
1. 張三在網上做了一筆交易,交易額100,買方小王通過銀行匯款100到張三的北京分行的賬號(見下面左圖),櫃檯操作人員向張三賬號存入100(事務一),然後系統些操作日誌(假設需要10秒,WAITFOR DELAY '00:00:10')正在此時張三在ATM查了一下賬號上餘額(事務二),發現已經是10100,於是回去準備發貨,但是事務一在寫操作日誌時超時,這是事務回滾,存款交易被取消,錢退給了小王,這樣張三查到的賬號餘額事實上是事務一還沒有提交的資料,導致張三錯誤的認為已經收到交易款項。
資料庫的事務隔離級別(TRANSACTION ISOLATION LEVEL)是一個數據庫上很基本的一個概念。為什麼會有事務隔離級別,SQL Server上實現了哪些事務隔離級別?事務隔離級別的前提是一個多使用者、多程序、多執行緒的併發系統,在這個系統中為了保證資料的一致性和完整性,我們引入了事務隔離級別這個概念,對一個單使用者、單執行緒的應用來說則不存在這個問題。
首先,我們來看一下高併發的系統中會存在哪些問題,為了便於理解我們以張三在招商銀行的賬號和存款為例。
一、準備工作:
1. 建立一個銀行賬號Table(只是為了說明問題,不考慮表的設計正規化)
CREATE TABLE dbo.BankAccount
(
BankAccountId CHAR(16) NOT NULL, -- 銀行賬號
UserName NVARCHAR(32) NOT NULL, -- 使用者
Balance DECIMAL(19, 2) NOT NULL, -- 餘額
LastUpdate SMALLDATETIME NOT NULL
)
GO
2. 準備資料
INSERT INTO dbo.BankAccount
VALUES ('9555500100071120', N'張三', 10000.00, GETDATE()) -- 北京分行賬號
INSERT INTO dbo.BankAccount
VALUES ('9555507551227787', N'張三', 20000.00, GETDATE()) -- 深圳分行賬號
GO
3. 檢視資料
SELECT * FROM dbo.BankAccount
二、應用場景
假設張三在招商銀行開設了兩個賬號,一個是招商銀行北京分行,一個是招商銀行深圳分行,兩個賬號的餘額分別是10,000和20,000。
1. 張三在網上做了一筆交易,交易額100,買方小王通過銀行匯款100到張三的北京分行的賬號(見下面左圖),櫃檯操作人員向張三賬號存入100(事務一),然後系統些操作日誌(假設需要10秒,WAITFOR DELAY '00:00:10')正在此時張三在ATM查了一下賬號上餘額(事務二),發現已經是10100,於是回去準備發貨,但是事務一在寫操作日誌時超時,這是事務回滾,存款交易被取消,錢退給了小王,這樣張三查到的賬號餘額事實上是事務一還沒有提交的資料,導致張三錯誤的認為已經收到交易款項。
一個事務讀到另外一個事務還沒有提交的資料,我們稱之為髒讀。
解決方法:把事務隔離級別調整到READ COMMITTED,即把右上圖中的SET TRAN ISOLATION LEVEL READ UNCOMMITTED更改成下圖中的SET TRAN ISOLATION LEVEL READ COMMITTED。這時我們重複上面的動作會發現事務二會一直等到事務一執行完畢再返回結果,因為此時事務以已經把自己的更改ROLLBACK了,所以事務二可以返回正確的結果。
2. 張三先後兩次查詢某一賬號的餘額,在兩次查詢期間,小王完成了銀行轉賬,導致兩次的查詢結果不同。
一個事務先後讀取同一條記錄,但兩次讀取的資料不同,我們稱之為不可重複讀。
解決方法:把事務隔離級別調整到REPEATABLE READ。在下圖中使用SET TRAN ISOLATION LEVEL REPEATABLE READ。這時我們重複上面的動作會發現事務二會一直等到事務一執行完畢再返回結果。
3. 張三妻子先後兩次查詢張三招商銀行所有賬號的總餘額,而在此期間張三在廣州招商銀行分行成功開設了一個賬號,並存入5000,導致張三妻子兩次查詢的總餘額不同,在此期間張三原有兩個賬號的餘額均未發生改變。
一個事務先後讀取一個範圍的記錄,但兩次讀取的紀錄數不同,我們稱之為幻象讀。
解決方法:把事務隔離級別調整到SERIALIZABLE。在下圖中使用SET TRAN ISOLATION LEVEL SERIALIZABLE。這時我們重複上面的動作會發現事務二會一直等到事務一執行完畢再返回結果。
三、總結
事務隔離級別是通過資料庫的鎖機制來控制的,在不同的應用場景需要應用不同的事務隔離級別,SQL Server預設的事務隔離級別是READ COMMITTED,預設的隔離級別,已經可以滿足我們大部分應用的需求。