【小资说库】第9期 数据库事务的隔离性保证机制-事务隔离级别
第6期中我们讲了什么是事务,及事务的ACID属性。其中ACID属性之一的隔离性Isolation提到:各个事务之间不会互相影响。即一个事务内部的操作及使用的数据对并发的启停事务是隔离的。并发执行的各个事务之间不能互相干扰。
从上面的描述不难看出,事务的隔离性针对的是并发事务的场景。那数据库是如何实现这个隔离性的呢?隔离性的保证机制有哪些呢?这一期我们就来扒一扒数据库事务隔离级别。
ANSI/ISO SQL标准定义了4中数据库事务隔离级别:未提交读(read uncommitted),提交读(read committed),重复读(repeatable read),串行读(serializable)。
对于不同的事务,采用不同的隔离级别分别有不同的结果。不同的隔离级别有不同的现象。主要有下面3种现在:
1、脏读(dirty read):一个事务可以读取另一个尚未提交事务的修改数据。
2、非重复读(nonrepeatable read):在同一个事务中,同一个查询在T1时间读取某一行,在T2时间重新读取这一行时候,这一行的数据已经发生修改,可能被更新了(update),也可能被删除了(delete)。
3、幻像读(phantom read):在同一事务中,同一查询多次进行时候,由于其他插入操作(insert)的事务提交,导致每次返回不同的结果集。
不可重复读出现多是因为修改;幻读重点是新增、删除。
不同的隔离级别有不同的现象,4种事务隔离级别分别表现的现象如下表:
隔离级别 | 脏读 | 非重复读 | 幻像读 |
read uncommitted | 允许 | 允许 | 允许 |
read committed | 允许 | 允许 | |
repeatable read | 允许 | ||
serializable |
小编,我看完,心里还是懵懵的。话说脏读是临时性尚未提交修改的数据,不应该读到确实是对的。而对于非重复读,我读两次数据之间别人正好修改或删除了数据,我看到两次数据不一样这很正常啊。幻像读也是啊,我的两次读之间,别人正好插入了数据,我本来就应该读到变化啊。讲真,没明白非重复读和幻像读有什么问题。
我们来通过网上的一个例子,尝试get下。
以MySQL的Innodb为例,其默认事务隔离级别是重复读(repeatable read)。
注:可以通过下面的命令查看mysql的隔离级别:
mysql> SELECT @@GLOBAL.tx_isolation, @@tx_isolation;
+———————–+—————–+
| @@GLOBAL.tx_isolation | @@tx_isolation |
+———————–+—————–+
| REPEATABLE-READ | REPEATABLE-READ |
+———————–+—————–+
1 row in set (0.00 sec)
下面对隔离级别进行一下测试:
Time | Session 1 | Session 2 | 解释 |
T1 | set autocommit=0; | set autocommit=0; | 将两个Session的事务自动提交机制取消。 即通过手动commit作为事务结束的标识。避免自动提交机制中把一句SQL作为一个事务这种机制对测试造成干扰。 |
T2 | mysql> select * from tmp_test;
+——+———+ | id | version | +——+———+ | 1 | 1 | +——+———+ 1 row in set (0.00 sec) |
Session1启动查询表 tmp_test的事务,并返回id=1,version=1的查询结果。 | |
T3 | mysql> update tmp_test set version=2 where id=1;
Query OK, 1 row affected (0.02 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select * from tmp_test; +——+———+ | id | version | +——+———+ | 1 | 2 | +——+———+ 1 row in set (0.00 sec) |
Session2中启动一条事务,更新表tmp_test,将version值改为2. | |
T4 | mysql> select * from tmp_test;
+——+———+ | id | version | +——+———+ | 1 | 1 | +——+———+ 1 row in set (0.00 sec) |
在Session1中再次查询表 tmp_test。Session 2未提交,看到数据不变,无脏读。返回值依然为id=1,version=1,并未出现Session2 | |
T5 | commit; | 提交Session2中的事务。 | |
T6 | mysql> select * from tmp_test;
+——+———+ | id | version | +——+———+ | 1 | 1 | +——+———+ 1 row in set (0.00 sec) |
在Session1中再次查询表 tmp_test。因Session1未提交,虽然Session 2已经提交,还是看到数据不变,即可以重复读。 | |
T7 | commit; | Session1事务提交。 | |
T8 | mysql> select * from tmp_test;
+——+———+ | id | version | +——+———+ | 1 | 2 | +——+———+ 1 row in set (0.00 sec) |
Session1提交事务后,新的查询事务中可以看到最新数据。体现了事务的原子性和隔离性。 | |
T9 | mysql> insert into tmp_test values(2,1);
Query OK, 1 row affected (0.00 sec) mysql> select * from tmp_test; +——+———+ | id | version | +——+———+ | 1 | 2 | | 2 | 1 | +——+———+ 2 rows in set (0.00 sec) mysql> commit; Query OK, 0 rows affected (0.00 sec) |
Session2中执行一个插入数据(id=2,version=1)的新事务,并提交。 | |
T10 | mysql> select * from tmp_test;
+——+———+ | id | version | +——+———+ | 1 | 2 | +——+———+ 1 row in set (0.00 sec) |
Session 2的insert事务已经提交,但Session1事务还未提交。看到的数据和T8的时候一样,即未发生幻象读。(这点和ANSI/ISO SQL标准定义的有所区别) | |
T11 | mysql> commit;
Query OK, 0 rows affected (0.00 sec) mysql> select * from tmp_test; +——+———+ | id | version | +——+———+ | 1 | 2 | | 2 | 1 | +——+———+ 2 rows in set (0.00 sec) |
事务提交,看到最新数据。 |
上面的结果可以看到Innodb的重复读(repeatable read)不允许脏读,不允许非重复读(即可以重复读)和不允许幻象读(这点和ANSI/ISO SQL标准定义的有所区别)。
正如前面所说,脏读是不可容忍的。而不可重复读和幻读到底需不需要呢,经过多方查阅及咨询专家发现:不同的业务场景下可以做不同选择。比如对于统计的场景,你肯定不喜欢你的查询总受其他人的事务影响导致查出来的结果反复变化,此时选择repeatable read隔离级别,即允许重复读更合适。而当你逛商场买单时,发现卡上没钱了,急等朋友转账时,你可能就更希望能实时刷新到卡上的到账信息,此时,read committed级别,即允许非重复读可能就更受欢迎了。
Oracle和SQL Server将read committed作为默认的隔离级别,而MySQL则将repeatable read作为默认的隔离级别。
编后随想:
小编突然想到一个有趣的事儿,假设我把上面mysql隔离级别测试中的autommit的值改为1,会怎样?大家可以试下并思考下为什么。答案就在【小资说库】第6期 什么是数据库事务?事务的ACID属性是什么?
相关参考链接:
https://www.cnblogs.com/kingcat/archive/2013/09/23/3334317.html
- 点赞
- 收藏
- 关注作者
评论(0)