事务的隔离级别与几个关键词有关: 脏数据
,不可重复读
,幻读
,一致性读
假设有一个 accounts 表:
diff
+----+---------+
| id | balance |
+----+---------+
| 1 | 1000.00 |
| 2 | 0.00 |
+----+---------+
READ UNCOMMITED (读未提交模式)
Shell A | Shell B |
---|---|
set session transaction isolation level read uncommitted; |
set session transaction isolation level read uncommitted; |
start transaction; |
start transaction; |
select * from accounts where id=1; |
select * from accounts where id=1; |
结果是: ` | 1 |
update accounts set balance=balance+100 where id=1; |
|
结果是: Query OK, 1 row affected (0.01 sec) |
|
select * from accounts where id=1; |
select * from accounts where id=1; |
结果是: ` | 1 |
rollback |
|
select * from accounts where id=1; |
select * from accounts where id=1; |
结果是: ` | 1 |
这个案例中, Shell B 执行 rollback 之前, Shell A 就读取到了 Shell B 存入的数据, 此时账户中的 100 就叫 脏数据, 如果 Shell B 在真实业务中执行了 rollback, 那此时 Shell A 中读取到的就是错误数据, 如果是在银行业务中出现了此类问题, 将会产生严重的后果
READ COMMITTED (读已提交模式)
Shell A | Shell B |
---|---|
set session transaction isolation level read committed; |
set session transaction isolation level read committed; |
start transaction; |
start transaction; |
select * from accounts where id=1; |
select * from accounts where id=1; |
结果是: ` | 1 |
update accounts set balance=balance+100 where id=1; |
|
结果是: Query OK, 1 row affected (0.01 sec) |
|
select * from accounts where id=1; |
select * from accounts where id=1; |
结果是: ` | 1 |
commit; |
|
select * from accounts where id=1; |
select * from accounts where id=1; |
结果是: ` | 1 |
这个模式中, 在 Shell B 的事务提交之前, Shell A 已经读取不到脏数据 了, 只有在 Shell B 提交后才能读取到 update
后的值
但是这还有一个问题: Shell A 在同一个事务中读取同一行的数据, 出现了不同的结果, 这是因为 Shell A 在执行事务期间, Shell B 的事务可以照常修改数据, 这种现象就叫 (Shell A)不可重复读
REPEATABLE READ (可重复读模式)
Shell A | Shell B |
---|---|
set session transaction isolation level repeatable read; |
set session transaction isolation level repeatable read; |
start transaction; |
start transaction; |
select * from accounts where id=1; |
select * from accounts where id=1; |
结果是: ` | 1 |
update accounts set balance=balance+100 where id=1; |
|
结果是: Query OK, 1 row affected (0.01 sec) |
|
select * from accounts where id=1; |
select * from accounts where id=1; |
结果是: ` | 1 |
commit; |
|
select * from accounts where id=1; |
select * from accounts where id=1; |
结果是: ` | 1 |
与READ COMMITTED
模式不同的是, 即便是 Shell B 中的提交了事务, Shell A 中查询出来的依然是1000
的余额, 解决了 不可重复读 问题, 这是因为该模式下,一次性读 机制让事务中的每次 select
都返回事务中第一次select
所建立的快照
另外, 在这个模式下, 即便其他事务对该事务所影响的行执行了删除操作, 在当前事务中, 依然能够看到被删除的数据
由于该模式还是允许其他事务修改数据, 所以可能出现一些非预期的情况, 下面这是官网的例子:
Shell A | Shell B |
---|---|
set session transaction isolation level repeatable read; |
set session transaction isolation level repeatable read; |
start transaction; |
start transaction; |
select COUNT(c2) from t1 where c2 = 'abc'; |
|
结果: Returns 0: no rows match. |
|
插入 10 条数据insert t1 values (...) |
|
commit; |
|
update t1 set c2 = 'cba' where c2 = 'abc'; |
|
select COUNT(c2) from t1 where c2 = 'cba'; |
|
结果: Returns 10 |
在这个例子中,Shell A 在 Shell B 提交后执行了update
语句, 导致把 Shell B 提交的数据也更新了, 这个现象就叫做 幻读(Shell A 更新了事务开始时不存在的数据), 在实际开发中也是很危险的行为
SERIALIZABLE (串行模式)
Shell A | Shell B |
---|---|
set session transaction isolation level serializable; |
set session transaction isolation level serializable; |
start transaction; |
start transaction; |
select count(id) from accounts'; |
|
结果: Returns 2 |
|
插入 1 条数据insert accounts values (...) |
|
暂停执行, 等待 Shell A 提交 | |
commit; |
自动执行 insert 语句 |
从过程中可以看到, Shell B 中的 insert 语句被暂停执行了, 这是啥因为 serializable 模式下, 所有普通的select
语句都被隐式添加了for share
的共享锁: 当前事务开启后, 其他事务只能读取被锁定的行, 不能修改; 这个案例中由于执行了count()
函数, 相当于是该表整个被锁定了, 所以 Shell B 也无法执行 insert
语句
这个模式就解决了幻读的问题
一致性读(consistent read)
一致性读 是 InnoDB 在 READ COMMITTED
和 REPEATABLE READ
隔离级别下处理 select
语句的默认模式。一致性读不会对其访问的表设置任何锁,因此在对表执行一致性读的同时,其他会话可以自由地修改这些表。
在READ COMMITTED
模式下, 一致性读 保证事务中每次 select
都会读取并设置其自己的最新快照
而在REPEATABLE READ
模式下, 一致性读 保证事务中每次 select
的结果都是相同的, 这是因为该模式下, 当发出一致读取(即普通的 SELECT 语句)时,InnoDB 会为所在事务提供一个时间点,后续事务内的查询将根据该时间点查看数据库