事務隔離
事務隔離(Isolation),指的是在數據庫系統中並發事務之間的可見性,以及如何相互影響的定義。事務隔離是ACID的四個特性(原子性、一致性、隔離性、持久性)之一。
目录 |
事務隔離級別 [编辑]
在ANSI/ISO的SQL標準中,定義了四個事務隔離級別,分別用於不同的場合。
可序列化 [编辑]
可序列化(SERIALIZABLE)是最高的隔離級別。在此級別下,所有事務的完整性都被保留,這意味着所有的事務可以被序列化地執行。當只有兩個事務之前沒有任何衝突時,纔能並行地執行。
可重複讀取 [编辑]
在可重複讀取(REPEATABLE READS)級別下,數據庫系統會在在整個事務期間保持讀寫鎖(read write lock),但相較於可序列化,範圍鎖(range-locks)不會被管理,所以幻象讀取(phantom reads)可能會出現。
授權讀取 [编辑]
在授權讀取(READ COMMITTED)級別下,數據庫系統在整個事務期間保持寫入鎖(write lock),但讀取鎖(read lock)會在SELECT執行後立即釋放,所以不可重複讀取(non-repeatable reads)可能會出現。
未授權讀取 [编辑]
未授權讀取(READ UNCOMMITTED)是最低的隔離級別。這個級別允許出現骯髒讀取(dirty reads)。
讀取現象 [编辑]
下列的示例解釋了幻象讀取(phantom reads)、不可重複讀取(non-repeatable reads)和骯髒讀取(dirty reads)。數據在下表中定義:
| id | name | age |
|---|---|---|
| 1 | Joe | 20 |
| 2 | Jill | 25 |
骯髒讀取 [编辑]
當一個事務試圖讀取另一個還未提交的事務正在修改的某一行數據時,骯髒讀取(dirty reads)就會發生。
下列示例中,事務2正在修改某行,但還沒有提交。事務1試圖讀取這一行。如果事務2回滾了(rolls back)變更,或者後面又進行了其他的修改,那麼事務1就獲得了骯髒(dirty)的數據。
| 事務1 | 事務2 |
|---|---|
/* Query 1 */ SELECT * FROM users WHERE id = 1; |
|
/* Query 2 */ UPDATE users SET age = 21 WHERE id = 1; /* No commit here */ |
|
/* Query 1 */ SELECT * FROM users WHERE id = 1; |
|
ROLLBACK; /* lock-based DIRTY READ */ |
不可重複讀取 [编辑]
當一個事務正在執行的時候,對某一行兩次讀取的結果不一致,則稱發生了不可重複讀取(non-repeatable reads)。
以下示例中事務1讀取了某行,之後事務2立刻修改了這一行並提交了結果,事務1再讀取這一行的時候,結果就不一致了。
| 事務1 | 事務2 |
|---|---|
/* Query 1 */ SELECT * FROM users WHERE id = 1; |
|
/* Query 2 */ UPDATE users SET age = 21 WHERE id = 1; COMMIT; /* in multiversion concurrency control, or lock-based READ COMMITTED */ |
|
/* Query 1 */ SELECT * FROM users WHERE id = 1; COMMIT; /* lock-based REPEATABLE READ */ |
幻象讀取 [编辑]
幻象讀取(phantom reads)指的是兩次集合查詢之間返回了不一致的結果。不一致一般是由其他事务新增数据导致。以下示例展現了這一現象。
| 事務1 | 事務2 |
|---|---|
/* Query 1 */ SELECT * FROM users WHERE age BETWEEN 10 AND 30; |
|
/* Query 2 */ INSERT INTO users VALUES ( 3, 'Bob', 27 ); COMMIT; |
|
/* Query 1 */ SELECT * FROM users WHERE age BETWEEN 10 AND 30; |
隔離級別、讀取現象與鎖 [编辑]
隔離級別與讀取現象 [编辑]
| 隔離級別 | 骯髒讀取 | 不可重複讀取 | 幻象讀取 |
|---|---|---|---|
| 未授權讀取 | 可能發生 | 可能發生 | 可能發生 |
| 授權讀取 | - | 可能發生 | 可能發生 |
| 可重複讀取 | - | - | 可能發生 |
| 可序列化 | - | - | - |
隔離級別與鎖 [编辑]
为了解决上面的4种问题,就出现了4种隔离级别,不同的数据库默认使用不同的隔离级别 1.read uncommit 当事务A更新某条数据时,不容许其他事务来更新该数据,但可以读取。 2.read commit 当事务A更新某条数据时,不容许其他事务进行任何操作包括读取,但事务A读取时,其他事务可以进行读取、更新 3.read repeatable 当事务A更新数据时,不容许其他事务进行任何操作,但当事务A进行读取时,其他事务只能读取,不能更新。 4.serializable 最严格的隔离级别,事务必须依次进行。
整理成表格如下:
| 隔離級別 | 寫入鎖 | 讀取鎖 | 範圍鎖 |
|---|---|---|---|
| READ UNCOMMITTED(允许读未提交数据) | V | - | - |
| READ COMMITED(只能读已提交数据) | V | S | - |
| REPEATABLE READ(事务内重复读同一块数据保持不变) | V | V | - |
| SERIALIZABLE(事务逐个串行操作) | V | V | V |
"V" 排他锁。锁定将持续到事务结束。
"S" 共享锁。一般写锁都是排他的,否则连最低的事务隔离级别都无法保证,会出现更新丢失(Lost update)的情况。读锁有共享读锁和排他读锁之分,两者都要等待对应范围的写锁被释放后才能获取,以避免读到脏数据;区别是前者仅在读取完成后即释放,而无需等到事务结束,且支持同时被多个读操作持有(意即共享)。
樂觀鎖與多版本並發控制 [编辑]
在鎖定控制的數據庫系統中,死鎖(dead lock)指的是兩個以上的事務互相依賴等待,從而都被阻塞的現象。與多線程程序的設計不同,鎖定控制的數據庫中死鎖出現是很正常現象,而且是無法根本上避免的。當死鎖出現並且被數據庫系統檢測到時,所有死鎖的事務都會被駁回,用戶不得不根據需要進行重提交。然而死鎖不僅檢測代價是很高昂的,而且還會浪費大量資源,如果死鎖頻繁出現,會大大降低數據庫系統的並發性能。但我們不能爲了避免死鎖而降低隔離等級,而且有一點可以肯定的是,事務隔離級別越高,死鎖出現的概率就越大。
爲了提高性能,樂觀鎖(optimistic locking)機制被提出。與傳統的悲觀鎖(pessimistic locking)「先取鎖再訪問」的保守策略不同,樂觀鎖相信事物之間的數據競爭(data race)的概率是比較小的,因此盡可能直接做下去,直到提交的時候纔去鎖定。但如果直接簡單這麼做,還是有可能會遇到不可預期的結果,例如兩個事務都讀取了數據庫的某一行,經過修改以後寫回數據庫,這時就遇到了問題。一種可靠的樂觀鎖的實現是使用「多版本控制(multi-version control)」,即在每一行加一個version屬性。修改這一行時將version增加1,寫回數據庫要檢查當前的version值是否還是獲取時的那個值了。如果還是,說明期間沒有其他事務對其修改,直接提交即可,如果已經不是了,說明期間已經有別的事務修改了這一行,當前事務獲取的數據已經過期了,事務失敗。
在PostgreSQL及MySQL的InnoDB引擎的實現中,多版本控制的樂觀鎖是內置的(build-in),所以這個無需手動添加version字段。默認情況下,PostgreSQL/MySQL會盡可能使用樂觀鎖,除非遇到顯式的鎖定命令,如「select * from sometable for update」這樣的語句,纔會主動使用悲觀鎖。
相關條目 [编辑]
外部鏈接 [编辑]
| 数据库管理系统(DBMS) () | |
|
概念 |
|
|
数据库组件 |
SQL |
| 数据库管理系统的实施 | |
|
实施类型 |
|
|
数据库成分 |
|