在 DBMS 中,事务保证了一个操作序列可以全部都执行或者全部都不执行(原子性),从一个状态转变到另外一个状态(一致性)。由于事务满足持久性,所以一旦事务被提交之后,数据就能够被持久化下来,又因为事务是满足隔离性的,因此当多个事务同时处理同一个数据的时候,多个事务直接是互不影响的,进而在多个事务并发操作的过程中,如果控制不好隔离级别,就有可能产生脏读、不可重复读或者幻读等读现象。
在数据库事务的ACID四个属性中,隔离性是最常放松的一个。咱们可以在数据操作过程中利用数据库的锁机制或者多版本并发控制机制获取更高的隔离等级。但是,随着数据库隔离级别的提高,数据的并发能力也会有所下降。因此,如何在并发性和隔离性之间做一个很好的权衡就成了一个至关重要的问题。
在软件开发中,几乎每类这样的问题都会有多种最佳实践来供咱们参考,很多 DBMS 定义了多个不同的“事务隔离等级”来控制锁的程度和并发能力。
ANSI/ISO SQL 定义的标准隔离级别有四种,从低到高依次为:未提交读(Read uncommitted)、提交读(Read committed)、可重复读(Repeatable reads)和可序列化(Serializable)。
下面,咱们就依次介绍这四种事务隔离级别的概念、用法以及解决了哪些问题或者说是读现象:
未提交读(Read uncommitted)
未提交读(Read uncommitted)是最低的隔离级别。通过名字咱们就可以知道,在这种事务隔离级别下,一个事务可以读到另外一个事务未提交的数据。
未提交读的数据库锁情况
- 事务在读数据的时候并未对数据加锁;
- 事务在修改数据的时候只对数据增加行级共享锁。
现象:
事务
T1读取某行记录时,事务T2也能对这行记录进行读取、更新,因为事务T1并未对数据增加任何锁。当事务
T2对该记录进行更新时,事务T1再次读取该记录,能读到事务T2对该记录的修改版本,因为事务T2只增加了共享读锁,事务T1可以再增加共享读锁读取数据,即使该修改尚未被提交。事务
T1更新某行记录时,事务T2不能对这行记录做更新,直到事务T1结束,因为事务T1对数据增加了共享读锁,事务T2不能增加排他写锁进行数据的修改。
举例:
T1 |
T2 |
|---|---|
SELECT age FROM users WHERE id = 1; /* will read 20 */ |
|
UPDATE users SET age = 21 WHERE id = 1; /* No commit here */ |
|
SELECT age FROM users WHERE id = 1; /* will read 21 */ |
|
ROLLBACK; /* lock-based DIRTY READ */ |
事务T1共查询了两次,在两次查询的过程中,事务T2对数据进行了修改,并未提交(commit)。但是事务T1的第二次查询查到了事务T2的修改结果。在数据库的“读现象”浅析中,咱们介绍过,这种现象称之为脏读。因此,未提交读会导致脏读。
提交读(Read committed)
提交读(Read committed)也可以翻译成“读已提交”,通过名字也可以分析出,在一个事务修改数据过程中,如果事务还没提交,其他事务不能读该数据。
提交读的数据库锁情况
- 事务对当前被读取的数据加行级共享锁(当读到时才加锁),一旦读完该行,立即释放该行级共享锁;
- 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁,直到事务结束才释放。
现象:
事务
T1在读取某行记录的整个过程中,事务T2都可以对该行记录进行读取,因为事务T1对该行记录增加行级共享锁的情况下,事务T2同样可以对该数据增加共享锁来读数据。事务
T1读取某行的一瞬间,事务T2不能修改该行数据,但是,只要事务T1读取完该行数据,事务T2就可以对该行数据进行修改。事务T1在读取的一瞬间会对数据增加共享锁,任何其他事务都不能对该行数据增加排他锁。但是事务T1只要读完该行数据,就会释放行级共享锁,一旦锁释放,事务T2就可以对数据增加排他锁并修改数据。事务
T1更新某行记录时,事务T2不能对这行记录做更新,直到事务T1结束。事务T1在更新数据的时候,会对该行数据增加排他锁,直到事务结束才会释放锁,因此,在事务T2没有提交之前,事务T1都能不对数据增加共享锁进行数据的读取。所以,提交读可以解决脏读的现象。
举例:
T1 |
T2 |
|---|---|
SELECT * FROM users WHERE id = 1; |
|
UPDATE users SET age = 21 WHERE id = 1; COMMIT; |
|
SELECT * FROM users WHERE id = 1; COMMIT; |
在提交读隔离级别中,在事务T2提交之前,事务T1不能读取数据。只有在事务T2提交之后,事务T1才能读数据。
但是从上面的例子中,咱们也可以看到,事务T1两次读取的结果并不一致,因此,提交读不能解决不可重复读的读现象。
简而言之,提交读这种隔离级别保证了读到的任何数据都是提交的数据,避免了脏读。但是不保证事务重新读的时候能读到相同的数据,因为在每次数据读完之后,其他事务可以修改刚才读到的数据。
可重复读(Repeatable reads)
由于提交读隔离级别会产生不可重复读的读现象,所以比提交读更高一个级别的隔离级别就可以解决不可重复读的问题,这种隔离级别就叫可重复读(Repeatable reads).
可重复读的数据库锁情况
- 事务在读取某数据的瞬间(就是开始读取的瞬间),必须先对其加行级共享锁,直到事务结束才释放;
- 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁,直到事务结束才释放。
现象:
事务
T1在读取某行记录的整个过程中,事务T2都可以对该行记录进行读取,因为事务T1对该行记录增加行级共享锁的情况下,事务T1同样可以对该数据增加共享锁来读数据。事务
T1在读取某行记录的整个过程中,事务T2都不能修改该行数据,事务T1在读取的整个过程会对数据增加共享锁,直到事务提交才会释放锁,所以整个过程中,任何其他事务都不能对该行数据增加排他锁。所以,可重复读能够解决不可重复读的读现象。
举例:
T1 |
T2 |
|---|---|
SELECT * FROM users WHERE id = 1; COMMIT; |
|
UPDATE users SET age = 21 WHERE id = 1; COMMIT; |
在上面的例子中,只有在事务T1提交之后,事务T2才能更改该行数据。所以,只要在事务T1从开始到结束的这段时间内,无论它读取该行数据多少次,结果都是一样的。
从上面的例子中,咱们可以得到结论:可重复读隔离级别可以解决不可重复读的读现象。但是可重复读这个隔离级别,还有另外一种读现象解决不了,那就是幻读。看看下面的例子:
T1 |
T2 |
|---|---|
SELECT * FROM users WHERE age BETWEEN 10 AND 30; |
|
INSERT INTO users VALUES ( 3, 'Bob', 27 ); COMMIT; |
|
SELECT * FROM users WHERE age BETWEEN 10 AND 30; |
上面的两个事务执行情况及现象如下:
- 事务
T1的第一次查询条件是age BETWEEN 10 AND如果这时有十条记录符合条件,它会给符合条件的这十条记录增加行级共享锁,任何其他事务都无法更改这十条记录。
30; - 事务
T2执行一条 SQL 语句,语句的内容是向表中插入一条数据,因为此时没有任何事务对表增加表级锁,所以该操作可以顺利执行。 - 事务
T1再次执行SELECT * FROM users WHERE age BETWEEN 10 AND 30;时,结果返回的记录变成了十一条,比刚刚增加了一条,增加的这条记录正是事务T2刚刚插入的那条。
所以,事务T1的两次范围查询结果并不相同,这也就是咱们说的幻读现象。
可序列化(Serializable)
可序列化(Serializable)是最高的隔离级别,前面提到的所有的隔离级别都无法解决的幻读,在可序列化的隔离级别中就可以解决。
咱们说过,产生幻读的原因是事务T1在进行范围查询的时候没有增加范围锁(range-locks:给SELECT的查询中使用一个WHERE子句描述范围加锁),所以导致幻读。
可序列化的数据库锁情况
- 事务在读取数据时,必须先对其加表级共享锁 ,直到事务结束才释放;
- 事务在更新数据时,必须先对其加表级排他锁 ,直到事务结束才释放。
现象:
事务
T1正在读取A表中的记录时,则事务T2也能读取A表,但不能对A表做更新、新增、删除,直到事务T1结束。因为事务T1对表增加了表级共享锁,其他事务只能增加共享锁读取数据,不能进行其他任何操作。事务
T1正在更新A表中的记录时,则事务T2不能读取A表的任意记录,更不可能对A表做更新、新增、删除,直到事务T1结束。因为事务T1对表增加了表级排他锁,其他事务不能对表增加共享锁或排他锁,也就无法进行任何操作。
虽然可序列化的隔离级别解决了脏读、不可重复读、幻读等读现象,但是序列化事务会产生以下效果:
- 无法读取其它事务已修改但未提交的记录。
- 在当前事务完成之前,其它事务不能修改目前事务已读取的记录。
- 在当前事务完成之前,其它事务所插入的新记录,其索引键值不在当前事务的任何语句所读取的索引键范围中。
四种事务隔离级别从隔离程度上越来越高,但同时在并发性上也就越来越低。之所以有这么几种隔离级别,就是为了方便开发人员在开发过程中根据业务需要选择最合适的隔离级别。
转载声明:本文转自「Hollis」,深入分析事务的隔离级别。