數據庫事務(Database Transaction) ,是指做爲單個邏輯工做單元執行的一系列操做,要麼徹底地執行,要麼徹底地不執行。
ACID,是指在可靠數據庫管理系統(DBMS)中,事務(Transaction)所應該具備的四個特性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)。
隔離級別 | 讀數據一致性 | 贓讀 | 不可重複讀 | 幻讀 |
---|---|---|---|---|
Read Uncommitted | 最低級別,只能保證不讀取物理上損壞的數據 | √ | √ | √ |
Read Committed | 語句級 | × | √ | √ |
Repeatable Read | 事務級 | × | × | √ |
Serializable | 最高級別,事務級 | × | × | × |
低級別的隔離通常支持更高的併發處理,並擁有更低的系統開銷。高級別的隔離可靠性較高,但系統開銷較大。spring
建立數據庫數據庫
CREATE DATABASE IF NOT EXISTS txdemo DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
建立測試表併發
CREATE TABLE `user` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `name` VARCHAR(32) NOT NULL DEFAULT '', `age` INT(16) NOT NULL DEFAULT '30', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
插入測試數據性能
INSERT INTO user (name, age) VALUES ('manerfan', 30), ('Abel', 28), ('Cherry', 42);
Step 1: 設置A的隔離級別爲Read Uncommitted,開啓事務並讀取數據
Step 2: B開啓事務,修改數據,但不提交
Step 3: A讀取數據,發現數據已變
Step 4: B回滾,但不提交
Step 5: A讀取數據,發現數據恢復測試
A事務中能夠讀取到B事務未修改的數據,發生贓讀spa
Step 1: 設置A的隔離級別爲Read Committed,開啓事務並讀取數據
Step 2: B開啓事務,修改數據,但不提交
Step 3: A讀取數據,發現數據未變
Step 4: B提交事務
Step 5: A讀取數據,發現數據改變線程
已提交讀隔離級別解決了髒讀的問題,可是出現了不可重複讀的問題,即事務A在兩次查詢的數據不一致,由於在兩次查詢之間事務B更新了一條數據。code
Step 1: 設置A的隔離級別爲Repeatable Read,開啓事務並讀取數據
Step 2: B開啓事務,修改數據,但不提交
Step 3: A讀取數據,發現數據未變
Step 4: B提交事務
Step 5: A讀取數據,發現數據依然未變,解決了不可重複讀
Step 6: B插入新數據,並提交
Step 7: A讀取數據,發現數據仍是未變,出現幻讀
Step 8: A提交事務,再次讀取數據,發現數據改變排序
Repeatable Read隔離級別只容許讀取已提交記錄,並且在一個事務兩次讀取一個記錄期間,其餘事務的更新不會影響該事務。但該事務不要求與其餘事務可串行化,可能會發生幻讀。索引
Step 1: 設置A的隔離級別爲Serializable,開啓事務並讀取數據
Step 2: B開啓事務,修改數據,B事務阻塞,A的事務還沒有提交,只能等待
Step 3: A事務提交,B事務插入成功,但B事務不提交
Step 4: A事務查詢,發現A事務阻塞,B的事務還沒有提交,只能等待
Step 5: B事務提交,A事務查詢成功
Serializable隔離級別徹底鎖定字段,若一個事務來查詢同一份數據就必須等待,直到前一個事務完成並解除鎖定爲止。
樂觀鎖(Optimistic Lock),是指操做數據庫時(更新操做),老是認爲此次的操做不會致使衝突,不到萬不得已不去拿鎖,在更新時採起判斷是否衝突,適用於讀操做遠多於更新操做的狀況。
樂觀鎖並無被數據庫實現,須要自行實現,一般的實現方式爲在表中增長版本version字段,更新時判斷庫中version與取出時的version值是否相等,若相等則執行更新並將version加1,若不相等則說明數據被其餘線程(進程)修改,放棄修改。
select (age, version) from user where id = #{id}; # 其餘操做 update user set age = 18, version = version + 1 where id = #{id} and version = #{version}
悲觀鎖(Pessimistic Lock),是指操做數據庫時(更新操做),老是認爲此次的操做會致使衝突,每次都要經過獲取鎖才能進行數據操做,所以要先確保獲取鎖成功再進行業務操做。
悲觀鎖須要數據庫自身提供支持,MySql提供了共享鎖和排他鎖來實現對數據行的鎖定,兩種鎖的介紹以下介紹。
InnoDB實現瞭如下兩種類型的行鎖:
X | S | |
---|---|---|
X | 衝突 | 衝突 |
S | 衝突 | 兼容 |
對於UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及數據集加排他鎖(X)
對於普通SELECT語句,InnoDB不會加任何鎖
事務能夠經過如下語句顯式地給記錄集加共享鎖或排他鎖:
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
,等同讀鎖SELECT * FROM table_name WHERE ... FOR UPDATE
,等同寫鎖用SELECT ... IN SHARE MODE
得到共享鎖,主要用在須要數據依存關係時來確認某行記錄是否存在,並確保沒有人對這個記錄進行UPDATE或者DELETE操做。可是若是當前事務也須要對該記錄進行更新操做,則頗有可能形成死鎖,對於鎖定行記錄後須要進行更新操做的應用,應該使用SELECT... FOR UPDATE
方式得到排他鎖。
SELECT ... IN SHARE MODE
模式
Step 1: A查詢id爲1的數據並加共享鎖
Step 2: B查詢id爲2的數據並加共享鎖
Step 3: A更新id爲2的數據,因爲共享鎖與排他鎖衝突而阻塞
Step 4: B更新id爲1的數據,因爲A與B互相等待對方釋放鎖而拋出死鎖異常
SELECT... FOR UPDATE
模式
Step 1: A查詢id爲1的數據並加排他鎖
Step 2: B查詢id爲1的數據不加任何鎖,成功
Step 3: B查詢id爲1的數據並加排他鎖,阻塞
Step 4: A更新id爲1的數據(數據庫自動加排他鎖),成功
Step 5: A提交事務,B獲取鎖查詢成功
InnoDB行鎖是經過給索引上的索引項加鎖來實現的,這一點MySQL與Oracle不一樣,後者是經過在數據塊中對相應數據行加鎖來實現的。InnoDB這種行鎖實現特色意味着:只有經過索引條件檢索數據,InnoDB才使用行級鎖,不然,InnoDB將使用表鎖!
在實際應用中,要特別注意InnoDB行鎖的這一特性,否則的話,可能致使大量的鎖衝突,從而影響併發性能。
Step 1: A查詢id爲1的數據並加排他鎖
Step 2: B查詢id爲2的數據並加排他鎖,成功
Step 3: A開啓新的事物,查詢age爲32的數據並加排他鎖
Step 4: B開啓新的事物,查詢age爲92的數據並加排他鎖,阻塞,B與A查詢的數據並非同一行,但B阻塞,說明A的排他鎖爲表鎖非行鎖
Spring的 @Transactional 提供了設置事務隔離級別及事務傳播行爲的方式
Isolation中定義了DEFAULT及以上介紹的四中隔離級別,這裏再也不贅述
package org.springframework.transaction.annotation; public enum Isolation { DEFAULT, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE; }
Propagation中定義了其中傳播行爲
package org.springframework.transaction.annotation; public enum Propagation { REQUIRED, SUPPORTS, MANDATORY, REQUIRES_NEW, NOT_SUPPORTED, NEVER, NESTED; }