事務隔離:修订间差异
→隔離級別與鎖: 調整格式排版 |
内容扩充 基于最新英文条目的翻译更新。 |
||
第1行: | 第1行: | ||
{{Refimprove|date=2009年1月}} |
|||
'''事務隔離(Isolation)''',指的是在[[數據庫]]系統中並發事務之間的可見性,以及如何相互影響的定義。事務隔離是[[ACID]]的四個特性(原子性、一致性、隔離性、持久性)之一。 |
|||
'''事务隔离(isolation)'''定义了[[database|数据库]]系统中一个操作产生的影响什么时候以哪种方式可以对其他[[Concurrency (computer science)|并发]]操作可见。隔离是事务[[ACID]] (原子性、一致性性、隔离性、持久性)四大属性中的一个重要属性。 |
|||
== 事務隔離級別 == |
|||
==并发控制(Concurrency control)== |
|||
[[Concurrency control|并发控制]]描述了数据库处理隔离以保证数据正确性的机制。为了保证并行事务执行的准确执行数据库和存储引擎在设计的时候着重强调了这一点。典型的事务相关机制限制数据的访问顺序([[Schedule (computer science)|执行调度]])以满足[[serializability|可序列化]] 和[[recoverability|可恢复性]]。限制数据访问意味着降低了执行的性能,并发控制机制就是要保证在满足这些限制的前提下提供尽可能高的性能。经常在不损害正确性的情况下,为了达到更好的性能,[[serializability|可序列化]]的的要求会减低一些,但是为了避免数据一致性的破坏,[[recoverability|可恢复性]]必须保证。 |
|||
[[Two-phase locking|两阶段锁]]是关系数据库中最常见的提供了[[serializability|可序列化]] 和[[recoverability|可恢复性]]的并发控制机制,为了访问一个数据库对象,事务首先要获得这个对象的[[Lock (database)| 锁]]。对于不同的访问类型(如对对象的读写操作)和锁的类型,如果另外一个事务正持有这个对象的锁,获得锁的过程会被阻塞或者延迟。 |
|||
在[[ANSI]]/[[ISO]]的[[SQL]]標準中,定義了四個事務隔離級別,分別用於不同的場合。 |
|||
==隔离级别(Isolation levels)== |
|||
=== 可序列化 === |
|||
在[[数据库]]事务的[[ACID]]四个属性中,隔离性是一个最常放松的一个。为了获取更高的隔离等级,数据库系统的[[Lock (database)| 锁]]机制或者[[multiversion concurrency control|多版本并发控制]]机制都会影响[[concurrency (computer science)|并发]]。 [[software application|应用软件]]也需要额外的逻辑来使其正常工作。 |
|||
''可序列化(SERIALIZABLE)''是''最高''的隔離級別。在此級別下,所有事務的完整性都被保留,這意味着所有的事務可以被[[序列化]]地執行。當只有兩個事務之间沒有任何衝突時,纔能[[並行]]地執行。 |
|||
很多[[DBMS]]定义了不同的“事务隔离等级”来控制锁的程度。在很多数据库系统中,多数的数据库事务都避免高等级的隔离等级(如可序列化)从而减少对系统的锁定开销。程序员需要小心的分析数据库访问部分的代码来保证隔离级别的降低不会造成难以发现的代码bug。相反的,更高的隔离级别会增加[[deadlock|死锁]]发生的几率,同样需要编程过程中去避免。 |
|||
=== 可重複讀取 === |
|||
在''可重複讀取(REPEATABLE READS)''級別下,數據庫系統會在在整個事務期間保持'''''讀寫鎖(read write lock)''''',但相較於'''可序列化''','''''範圍鎖(range-locks)'''''不會被管理,所以'''''幻象讀取(phantom reads)'''''可能會出現。 |
|||
[[American National Standards Institute|ANSI]]/[[International Organization for Standardization|ISO]] [[SQL]]定义的标准隔离级别如下。 |
|||
=== 授權讀取 === |
|||
在''授權讀取(READ COMMITTED)''級別下,數據庫系統在整個事務期間保持'''''寫入鎖(write lock)''''',但'''''讀取鎖(read lock)'''''會在[[SELECT]]執行後立即釋放,所以'''''不可重複讀取(non-repeatable reads)'''''可能會出現。 |
|||
===可序列化(Serializable)=== |
|||
=== 未授權讀取 === |
|||
最高的隔离级别。 |
|||
''未授權讀取(READ UNCOMMITTED)''是''最低''的隔離級別。這個級別允許出現'''''骯髒讀取(dirty reads)'''''。 |
|||
在基于锁机制[[concurrency control|并发控制]]的DBMS实现[[serializability|可序列化]]要求在选定对象上的读锁和写锁保持直到事务结束后才能释放。在[[Select (SQL)|SELECT]] 的查询中使用一个“WHERE”子句来描述一个范围时应该获得一个“范围锁(range-locks)”。这种机制可以避免“幻影读(phantom reads)”现象。 |
|||
== 讀取現象 == |
|||
当采用不基于锁的[[concurrency control|并发控制]]时不用获取锁。但当系统探测到几个并发事务有“写冲突”的时候,只有其中一个是允许提交的。这种机制的详细描述见“'[[snapshot isolation|快照隔离]]” |
|||
下列的示例解釋了'''''幻象讀取(phantom reads)'''''、'''''不可重複讀取(non-repeatable reads)'''''和'''''骯髒讀取(dirty reads)'''''。數據在下表中定義: |
|||
===可重复读(Repeatable reads)=== |
|||
在可重复读(REPEATABLE READS)隔离级别中,基于锁机制[[concurrency control|并发控制]]的DBMS需要对选定对象的读锁(read locks)和写锁(write locks)一直保持到事务结束,但不要求“范围锁(range-locks)”,因此可能会发生“幻影读(phantom reads)” |
|||
===授权读(Read committed)=== |
|||
在授权读(READ COMMITTED)级别中,基于锁机制[[concurrency control|并发控制]]的DBMS需要对选定对象的写锁(write locks)一直保持到事务结束,但是读锁(read locks)在[[Select (SQL)|SELECT]]操作完成后马上释放(因此“不可重复读”现象可能会发生,见下面描述)。和前一种隔离级别一样,也不要求“范围锁(range-locks)”。 |
|||
简而言之,授权读这种隔离级别保证了读到的任何数据都是提交的数据,避免读到中间的未提交的数据,'''''脏读(dirty reads)'''''。但是不保证事务重新读的时候能读到相同的数据,因为在每次数据读完之后其他事务可以修改刚才读到的数据。 |
|||
===未授权读(Read uncommitted)=== |
|||
未授权读(READ UNCOMMITTED)是最低的隔离级别。允许'''''脏读(dirty reads)''''',事务可以看到其他事务“尚未提交”的修改。 |
|||
通过比低一级的隔离级别要求更多的限制,高一级的级别提供更强的隔离性。标准允许事务运行在更强的事务隔离级别上。(如在可重复读(REPEATABLE READS)隔离级别上执行授权读(READ COMMITTED)的事务是没有问题的) |
|||
==默认隔离级别== |
|||
不同的[[Database management system|DBMS]]默认隔离级别也不同。多少数据库允许用户设置隔离级别。有些DBMS在执行一个SELECT语句时使用额外的语法来获取锁(如''SELECT ... FOR UPDATE''来获得在访问的数据行上的排他锁) |
|||
==读现象(Read phenomena)== |
|||
ANSI/ISO 标准SQL 92涉及三种不同的一个事务读取另外一个事务可能修改的数据的“读现象”。 |
|||
下面的例子中,两个事务,事务1执行语句1。接着,事务2执行语句2并且提交,最后事务1再执行语句1. |
|||
查询使用如下的数据表。 |
|||
{|class="wikitable" |
{|class="wikitable" |
||
|+ users |
|+ users |
||
第30行: | 第51行: | ||
|} |
|} |
||
===脏读(Dirty reads (Uncommitted Dependency))=== |
|||
=== 骯髒讀取 === |
|||
当一个事务允许读取另外一个事务修改但未提交的数据时,就可能发生脏读(dirty reads)。 |
|||
脏读(dirty reads)和不可重复读(non-repeatable reads)类似。事务2没有提交造成事务1的语句1两次执行得到不同的结果集。在未授权读(READ UNCOMMITTED)隔离级别唯一禁止的是更新混乱,即早期的更新可能出现在后来更新之前的结果集中。 |
|||
下列示例中,事務2正在修改某行,但還沒有提交。事務1試圖讀取這一行。如果事務2回滾了(rolls back)變更,或者後面又進行了其他的修改,那麼事務1就獲得了''骯髒(dirty)''的數據。 |
|||
在我们的例子中,事务2修改了一行,但是没有提交,事务1读了这个没有提交的数据。现在如果事务2回滚了刚才的修改或者做了另外的修改的话,事务1中查到的数据就是不正确的了。 |
|||
{|style="font-size: 94%;" |
{|style="font-size: 94%;" |
||
|- |
|- |
||
! 事 |
! 事务 1 |
||
! 事 |
! 事务 2 |
||
|- |
|- |
||
|<source lang="sql"> |
|<source lang="sql"> |
||
/* Query 1 */ |
/* Query 1 */ |
||
SELECT |
SELECT age FROM users WHERE id = 1; |
||
/* will read 20 */ |
|||
</source> |
</source> |
||
| |
| |
||
第56行: | 第79行: | ||
|<source lang="sql"> |
|<source lang="sql"> |
||
/* Query 1 */ |
/* Query 1 */ |
||
SELECT |
SELECT age FROM users WHERE id = 1; |
||
/* will read 21 */ |
|||
</source> |
</source> |
||
| |
| |
||
第66行: | 第90行: | ||
|} |
|} |
||
在这个例子中,事务2回滚后就没有id是1,age是21的数据行了。 |
|||
=== 不可重複讀取 === |
|||
===不可重复读(non-repeatable read)=== |
|||
當一個事務正在執行的時候,對某一行兩次讀取的結果不一致,則稱發生了'''''不可重複讀取(non-repeatable reads)'''''。 |
|||
在一次事务中,当一行数据获取两遍得到不同的结果表示发生了“不可重复读(non-repeatable read)”. |
|||
在基于锁的并发控制中“不可重复读(non-repeatable read)”现象发生在当执行[[Select (SQL)|SELECT]] 操作时没有获得读锁(read locks)或者[[Select (SQL)|SELECT]]操作执行完后马上释放了读锁; [[multiversion concurrency control|多版本并发控制]]中当没有要求一个[[commit conflict|提交冲突]]的事务回滚也会发生“不可重复读(non-repeatable read)”现象。 |
|||
以下示例中事務1讀取了某行,之後事務2立刻修改了這一行並提交了結果,事務1再讀取這一行的時候,結果就不一致了。 |
|||
{|style="font-size: 94%;" |
{|style="font-size: 94%;" |
||
|- |
|- |
||
! 事 |
! 事务 1 |
||
! 事 |
! 事务 2 |
||
|- |
|- |
||
|<source lang="sql"> |
|<source lang="sql"> |
||
第98行: | 第123行: | ||
|} |
|} |
||
=== 幻象讀取 === |
|||
在这个例子中,事务2提交成功,因此他对id为1的行的修改就对其他事务可见了。但是事务1在此前已经从这行读到了另外一个“age”的值。在可序列化(SERIALIZABLE)和可重复读(REPEATABLE READS)的隔离级别,数据库在第二次SELECT请求的时候应该返回事务2更新之前的值。在授权读(READ COMMITTED)和未授权读(READ UNCOMMITTED),返回的是更新之后的值,这个现象就是不可重复读(non-repeatable read)。 |
|||
'''''幻象讀取(phantom reads)'''''指的是兩次集合查詢之間返回了不一致的結果。不一致一般是由其他事务新增数据导致。以下示例展現了這一現象。 |
|||
有两种策略可以避免不可重复读(non-repeatable read)。一个是要求事务2延迟到事务1提交或者回滚之后再执行。这种方式实现了'''T1, T2''' 的串行化[[Schedule (computer science)|调度]]。串行化调度可以支持可重复读(repeatable reads)。 |
|||
另一种策略是''[[multiversion concurrency control|多版本并发控制]]''。为了得到更好的并发性能,允许事务2先提交。但因为事务1在事务2之前开始,事务1必须在其开始执行时间点的数据库的快照上面操作。当事务1最终提交时候,数据库会检查其结果是否等价于'''T1, T2'''串行调度。如果等价,则允许事务1提交,如果不等价,事务1需要回滚并抛出个串行化失败的错误。 |
|||
使用基于锁的并发控制,在可重复读(REPEATABLE READS)的隔离级别中,ID=1的行会被锁住,在事务1提交或回滚前一直阻塞语句2的执行。在授权读(READ COMMITTED)的级别,语句1第二次执行,age已经被修改了。 |
|||
在''[[multiversion concurrency control|多版本并发控制]]''机制下,可序列化(SERIALIZABLE)级别,两次SELECT语句读到的数据都是事务1开始的快照,因此返回同样的数据。但是,如果事务1试图UPDATE这行数据,事务1会被要求回滚并抛出一个串行化失败的错误。 |
|||
在授权读(READ COMMITTED)隔离级别,每个语句读到的是语句执行前的快照,因此读到更新前后不同的值。在这种级别不会有串行化的错误(因为这种级别不要求串行化),事务1也不要求重试。 |
|||
===幻影读(phantom read)=== |
|||
在事务执行过程中,当两个完全相同的查询语句执行得到不同的结果集。这种现象称为“幻影读(phantom read)” |
|||
当事务没有获取''[[range locks|范围锁]]''的情况下执行''[[Select (SQL)|SELECT]] ... WHERE''操作可能会发生“幻影读(phantom read)”。 |
|||
“幻影读(phantom read)”是''不可重复读(Non-repeatable reads)''的一种特殊场景:当事务1两次执行''SELECT ... WHERE''检索一定范围内数据的操作中间,事务2在这个表中创建了(如[[INSERT]])了一行新数据,这条新数据正好满足事务1的“WHERE”子句。 |
|||
{|style="font-size: 95%;" |
{|style="font-size: 95%;" |
||
|- |
|- |
||
! 事 |
! 事务 1 |
||
! 事 |
! 事务 2 |
||
|- |
|- |
||
|<source lang="sql"> |
|<source lang="sql"> |
||
第129行: | 第171行: | ||
|} |
|} |
||
需要指出的是事务1执行了两遍同样的查询语句。如果设了最高的隔离级别,两次会得到同样的结果集,这也正是可数据库在序列化(SERIALIZABLE)隔离级别上需要满足的。但是在较低的隔离级别上,第二次查询可能会得到不同的结果集。 |
|||
== 隔離級別、讀取現象與鎖 == |
|||
=== 隔離級別與讀取現象 === |
|||
在可序列化(SERIALIZABLE)隔离级别,查询语句1在age从10到30的记录上加锁,事务2只能阻塞直至事务1提交。在可重复读(REPEATABLE READ)级别,这个范围不会被锁定,允许记录插入,因此第二次执行语句1的结果中会包括新插入的行。 |
|||
==隔离级别、读现象和锁(Isolation Levels, Read Phenomena and Locks)== |
|||
===隔离级别vs读现象(Isolation Levels vs Read Phenomena)=== |
|||
{|class="wikitable" |
{|class="wikitable" |
||
! 隔 |
! 隔离级别 !! 脏读 !!不可重复读 !!幻影读 |
||
|- |
|- |
||
| |
|未授权读 || 可能发生||可能发生 ||可能发生 |
||
|- |
|- |
||
| |
|授权读 || - || 可能发生 || 可能发生 |
||
|- |
|- |
||
| |
|可重复读 || - || - ||可能发生 |
||
|- |
|- |
||
| |
|可序列化 || - || - || - |
||
|} |
|} |
||
可序列化(Serializable)隔离级别不等同于可串行化(Serializable)。可串行化调度(Serializable)是避免以上三种现象的必要条件,但不是充分条件。 |
|||
=== 隔離級別與鎖 === |
|||
为了解决上面的4种问题,就出现了4种隔离级别,不同的数据库默认使用不同的隔离级别 |
|||
“可能发生”表示这个隔离级别会发生对应的现象,“-”表示不会发生。 |
|||
# read uncommit 当事务A更新某条数据时,不容许其他事务来更新该数据,但可以读取。 |
|||
# read commit 当事务A更新某条数据时,不容许其他事务进行任何操作包括读取,但事务A读取时,其他事务可以进行读取、更新 |
|||
# read repeatable 当事务A更新数据时,不容许其他事务进行任何操作,但当事务A进行读取时,其他事务只能读取,不能更新。 |
|||
# serializable 最严格的隔离级别,事务必须依次进行。 |
|||
===隔离级别vs 锁持续时间(Isolation Levels vs Lock Duration) === |
|||
整理成表格如下: |
|||
在基于锁的并发控制中,隔离级别决定了锁的持有时间。'''"C"'''-表示锁会持续到事务提交。 |
|||
'''"S"''' –表示锁持续到当前语句执行完毕。如果锁在语句执行完毕就释放则另外一个事务就可以在这个事务提交前修改锁定的数据,从而造成混乱。 |
|||
{|class="wikitable" |
{|class="wikitable" |
||
! 隔 |
! 隔离级别l !! 写操作 !!读操作 !!范围操作 (...where...) |
||
|- |
|- |
||
| |
| 未授权读 || S || S || S |
||
|- |
|- |
||
| |
| 授权读 || C || S || S |
||
|- |
|- |
||
| |
| 可重复读 || C || C || S |
||
|- |
|- |
||
| |
| 可序列化 || C || C || C |
||
|} |
|} |
||
==参照== |
|||
"V" 排他锁。锁定将持续到事务结束。 |
|||
{{reflist}} |
|||
"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」這樣的語句,纔會主動使用[[悲觀鎖]]。 |
|||
== |
==相关条目== |
||
* [[Atomicity (database systems)|原子性]] |
|||
* [[ACID]] |
|||
* [[Consistency (database systems)|一致性]] |
|||
* [[並發控制]] |
|||
* [[Durability (database systems)|持久性]] |
|||
* [[關係數據庫]] |
|||
* [[Lock (database)|锁]] |
|||
* [[Optimistic concurrency control|乐观并发控制]] |
|||
* [[Relational Database Management System|关系数据库]] |
|||
* [[Snapshot isolation|快照隔离]] |
|||
== |
==外部链接== |
||
* [http://www.byvoid.com/blog/rdbms-isolation-lock-concurrency/ 關係數據庫的事務隔離、鎖定與並發控制] |
|||
* [http://planetofcoders.blogspot.com/2011/07/what-is-isolation-level.html what is isolation level] |
|||
* [http://docs.oracle.com/cd/B12037_01/server.101/b10743/toc.htm Oracle® Database Concepts], [http://docs.oracle.com/cd/B12037_01/server.101/b10743/consist.htm#sthref1919 chapter 13 Data Concurrency and Consistency, Preventable Phenomena and Transaction Isolation Levels] |
|||
{{Databases}} |
|||
* [http://docs.oracle.com/cd/B19306_01/server.102/b14200/toc.htm Oracle® Database SQL Reference], [http://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_10.htm#i2068385 chapter 19 SQL Statements: SAVEPOINT to UPDATE], [http://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_10005.htm#i2067247 SET TRANSACTION] |
|||
<!-- representations in api: java --> |
|||
* in [[Java Database Connectivity|JDBC]]: [http://docs.oracle.com/javase/7/docs/api/java/sql/Connection.html#field_summary Connection constant fields], [http://docs.oracle.com/javase/7/docs/api/java/sql/Connection.html#getTransactionIsolation() Connection.getTransactionIsolation()], [http://docs.oracle.com/javase/7/docs/api/java/sql/Connection.html#setTransactionIsolation(int) Connection.setTransactionIsolation(int)] |
|||
* in [[Spring Framework]]: [http://static.springsource.org/spring/docs/current/javadoc-api/org/springframework/transaction/annotation/Transactional.html @Transactional], [http://static.springsource.org/spring/docs/current/javadoc-api/org/springframework/transaction/annotation/Isolation.html Isolation] |
|||
<!-- representations in api: .NET_Framework --> |
|||
<!--Categories--> |
<!--Categories--> |
||
{{DEFAULTSORT:Isolation (Database Systems)}} |
|||
[[Category:数据库]] |
|||
[[Category:Data management]] |
|||
[[Category:Transaction processing]] |
|||
<!--Interwikies--> |
<!--Interwikies--> |
2014年1月5日 (日) 06:33的版本
此條目需要补充更多来源。 (2009年1月) |
事务隔离(isolation)定义了数据库系统中一个操作产生的影响什么时候以哪种方式可以对其他并发操作可见。隔离是事务ACID (原子性、一致性性、隔离性、持久性)四大属性中的一个重要属性。
并发控制(Concurrency control)
并发控制描述了数据库处理隔离以保证数据正确性的机制。为了保证并行事务执行的准确执行数据库和存储引擎在设计的时候着重强调了这一点。典型的事务相关机制限制数据的访问顺序(执行调度)以满足可序列化 和可恢复性。限制数据访问意味着降低了执行的性能,并发控制机制就是要保证在满足这些限制的前提下提供尽可能高的性能。经常在不损害正确性的情况下,为了达到更好的性能,可序列化的的要求会减低一些,但是为了避免数据一致性的破坏,可恢复性必须保证。
两阶段锁是关系数据库中最常见的提供了可序列化 和可恢复性的并发控制机制,为了访问一个数据库对象,事务首先要获得这个对象的 锁。对于不同的访问类型(如对对象的读写操作)和锁的类型,如果另外一个事务正持有这个对象的锁,获得锁的过程会被阻塞或者延迟。
隔离级别(Isolation levels)
在数据库事务的ACID四个属性中,隔离性是一个最常放松的一个。为了获取更高的隔离等级,数据库系统的 锁机制或者多版本并发控制机制都会影响并发。 应用软件也需要额外的逻辑来使其正常工作。
很多DBMS定义了不同的“事务隔离等级”来控制锁的程度。在很多数据库系统中,多数的数据库事务都避免高等级的隔离等级(如可序列化)从而减少对系统的锁定开销。程序员需要小心的分析数据库访问部分的代码来保证隔离级别的降低不会造成难以发现的代码bug。相反的,更高的隔离级别会增加死锁发生的几率,同样需要编程过程中去避免。
可序列化(Serializable)
最高的隔离级别。
在基于锁机制并发控制的DBMS实现可序列化要求在选定对象上的读锁和写锁保持直到事务结束后才能释放。在SELECT 的查询中使用一个“WHERE”子句来描述一个范围时应该获得一个“范围锁(range-locks)”。这种机制可以避免“幻影读(phantom reads)”现象。
当采用不基于锁的并发控制时不用获取锁。但当系统探测到几个并发事务有“写冲突”的时候,只有其中一个是允许提交的。这种机制的详细描述见“'快照隔离”
可重复读(Repeatable reads)
在可重复读(REPEATABLE READS)隔离级别中,基于锁机制并发控制的DBMS需要对选定对象的读锁(read locks)和写锁(write locks)一直保持到事务结束,但不要求“范围锁(range-locks)”,因此可能会发生“幻影读(phantom reads)”
授权读(Read committed)
在授权读(READ COMMITTED)级别中,基于锁机制并发控制的DBMS需要对选定对象的写锁(write locks)一直保持到事务结束,但是读锁(read locks)在SELECT操作完成后马上释放(因此“不可重复读”现象可能会发生,见下面描述)。和前一种隔离级别一样,也不要求“范围锁(range-locks)”。
简而言之,授权读这种隔离级别保证了读到的任何数据都是提交的数据,避免读到中间的未提交的数据,脏读(dirty reads)。但是不保证事务重新读的时候能读到相同的数据,因为在每次数据读完之后其他事务可以修改刚才读到的数据。
未授权读(Read uncommitted)
未授权读(READ UNCOMMITTED)是最低的隔离级别。允许脏读(dirty reads),事务可以看到其他事务“尚未提交”的修改。
通过比低一级的隔离级别要求更多的限制,高一级的级别提供更强的隔离性。标准允许事务运行在更强的事务隔离级别上。(如在可重复读(REPEATABLE READS)隔离级别上执行授权读(READ COMMITTED)的事务是没有问题的)
默认隔离级别
不同的DBMS默认隔离级别也不同。多少数据库允许用户设置隔离级别。有些DBMS在执行一个SELECT语句时使用额外的语法来获取锁(如SELECT ... FOR UPDATE来获得在访问的数据行上的排他锁)
读现象(Read phenomena)
ANSI/ISO 标准SQL 92涉及三种不同的一个事务读取另外一个事务可能修改的数据的“读现象”。
下面的例子中,两个事务,事务1执行语句1。接着,事务2执行语句2并且提交,最后事务1再执行语句1. 查询使用如下的数据表。
id | name | age |
---|---|---|
1 | Joe | 20 |
2 | Jill | 25 |
脏读(Dirty reads (Uncommitted Dependency))
当一个事务允许读取另外一个事务修改但未提交的数据时,就可能发生脏读(dirty reads)。
脏读(dirty reads)和不可重复读(non-repeatable reads)类似。事务2没有提交造成事务1的语句1两次执行得到不同的结果集。在未授权读(READ UNCOMMITTED)隔离级别唯一禁止的是更新混乱,即早期的更新可能出现在后来更新之前的结果集中。
在我们的例子中,事务2修改了一行,但是没有提交,事务1读了这个没有提交的数据。现在如果事务2回滚了刚才的修改或者做了另外的修改的话,事务1中查到的数据就是不正确的了。
事务 1 | 事务 2 |
---|---|
/* Query 1 */
SELECT age FROM users WHERE id = 1;
/* will read 20 */
|
|
/* Query 2 */
UPDATE users SET age = 21 WHERE id = 1;
/* No commit here */
| |
/* Query 1 */
SELECT age FROM users WHERE id = 1;
/* will read 21 */
|
|
ROLLBACK; /* lock-based DIRTY READ */
|
在这个例子中,事务2回滚后就没有id是1,age是21的数据行了。
不可重复读(non-repeatable read)
在一次事务中,当一行数据获取两遍得到不同的结果表示发生了“不可重复读(non-repeatable read)”.
在基于锁的并发控制中“不可重复读(non-repeatable read)”现象发生在当执行SELECT 操作时没有获得读锁(read locks)或者SELECT操作执行完后马上释放了读锁; 多版本并发控制中当没有要求一个提交冲突的事务回滚也会发生“不可重复读(non-repeatable read)”现象。
事务 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 */
|
在这个例子中,事务2提交成功,因此他对id为1的行的修改就对其他事务可见了。但是事务1在此前已经从这行读到了另外一个“age”的值。在可序列化(SERIALIZABLE)和可重复读(REPEATABLE READS)的隔离级别,数据库在第二次SELECT请求的时候应该返回事务2更新之前的值。在授权读(READ COMMITTED)和未授权读(READ UNCOMMITTED),返回的是更新之后的值,这个现象就是不可重复读(non-repeatable read)。
有两种策略可以避免不可重复读(non-repeatable read)。一个是要求事务2延迟到事务1提交或者回滚之后再执行。这种方式实现了T1, T2 的串行化调度。串行化调度可以支持可重复读(repeatable reads)。
另一种策略是多版本并发控制。为了得到更好的并发性能,允许事务2先提交。但因为事务1在事务2之前开始,事务1必须在其开始执行时间点的数据库的快照上面操作。当事务1最终提交时候,数据库会检查其结果是否等价于T1, T2串行调度。如果等价,则允许事务1提交,如果不等价,事务1需要回滚并抛出个串行化失败的错误。
使用基于锁的并发控制,在可重复读(REPEATABLE READS)的隔离级别中,ID=1的行会被锁住,在事务1提交或回滚前一直阻塞语句2的执行。在授权读(READ COMMITTED)的级别,语句1第二次执行,age已经被修改了。
在多版本并发控制机制下,可序列化(SERIALIZABLE)级别,两次SELECT语句读到的数据都是事务1开始的快照,因此返回同样的数据。但是,如果事务1试图UPDATE这行数据,事务1会被要求回滚并抛出一个串行化失败的错误。
在授权读(READ COMMITTED)隔离级别,每个语句读到的是语句执行前的快照,因此读到更新前后不同的值。在这种级别不会有串行化的错误(因为这种级别不要求串行化),事务1也不要求重试。
幻影读(phantom read)
在事务执行过程中,当两个完全相同的查询语句执行得到不同的结果集。这种现象称为“幻影读(phantom read)”
当事务没有获取范围锁的情况下执行SELECT ... WHERE操作可能会发生“幻影读(phantom read)”。
“幻影读(phantom read)”是不可重复读(Non-repeatable reads)的一种特殊场景:当事务1两次执行SELECT ... WHERE检索一定范围内数据的操作中间,事务2在这个表中创建了(如INSERT)了一行新数据,这条新数据正好满足事务1的“WHERE”子句。
事务 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;
|
需要指出的是事务1执行了两遍同样的查询语句。如果设了最高的隔离级别,两次会得到同样的结果集,这也正是可数据库在序列化(SERIALIZABLE)隔离级别上需要满足的。但是在较低的隔离级别上,第二次查询可能会得到不同的结果集。
在可序列化(SERIALIZABLE)隔离级别,查询语句1在age从10到30的记录上加锁,事务2只能阻塞直至事务1提交。在可重复读(REPEATABLE READ)级别,这个范围不会被锁定,允许记录插入,因此第二次执行语句1的结果中会包括新插入的行。
隔离级别、读现象和锁(Isolation Levels, Read Phenomena and Locks)
隔离级别vs读现象(Isolation Levels vs Read Phenomena)
隔离级别 | 脏读 | 不可重复读 | 幻影读 |
---|---|---|---|
未授权读 | 可能发生 | 可能发生 | 可能发生 |
授权读 | - | 可能发生 | 可能发生 |
可重复读 | - | - | 可能发生 |
可序列化 | - | - | - |
可序列化(Serializable)隔离级别不等同于可串行化(Serializable)。可串行化调度(Serializable)是避免以上三种现象的必要条件,但不是充分条件。
“可能发生”表示这个隔离级别会发生对应的现象,“-”表示不会发生。
隔离级别vs 锁持续时间(Isolation Levels vs Lock Duration)
在基于锁的并发控制中,隔离级别决定了锁的持有时间。"C"-表示锁会持续到事务提交。 "S" –表示锁持续到当前语句执行完毕。如果锁在语句执行完毕就释放则另外一个事务就可以在这个事务提交前修改锁定的数据,从而造成混乱。
隔离级别l | 写操作 | 读操作 | 范围操作 (...where...) |
---|---|---|---|
未授权读 | S | S | S |
授权读 | C | S | S |
可重复读 | C | C | S |
可序列化 | C | C | C |
参照
相关条目
外部链接
- Oracle® Database Concepts, chapter 13 Data Concurrency and Consistency, Preventable Phenomena and Transaction Isolation Levels
- Oracle® Database SQL Reference, chapter 19 SQL Statements: SAVEPOINT to UPDATE, SET TRANSACTION
- in JDBC: Connection constant fields, Connection.getTransactionIsolation(), Connection.setTransactionIsolation(int)
- in Spring Framework: @Transactional, Isolation