- 脏读
事务1 可以访问 事务2 中未提交的数据
会话1 | 会话2 |
---|---|
begin | begin |
update table set age = 10 where id = 1 | |
select age from table where id = 1 | |
commit | commit / rollback |
会话2将age的值改变,且并未提交,此时会话1select查询结果为10.
如果会话2 最终的结果是commit,那么此查询没有问题
但是如果会话2 最终选择rollback回滚,那么会话1 的查询结果是不正确的。
- 不可重复读
事务1 中包括了多次查询,但是结果却不一样
会话1 | 会话2 |
---|---|
begin | begin |
select age from table where id = 1 | |
update table set age = 10 where id = 1 | |
commit | |
select age from table where id = 1 | |
commit |
会话1 两次查询结果不一致,原因是会话2 进行了事务的提交。
需要注意的是两次查询是在同一个事务中的。
- 幻读
事务1 中包括了多次查询,但是结果数量却不一样(数据增多或减少)
会话1 | 会话2 |
---|---|
begin | begin |
select age from table where id > 2 | |
insert into table(id,age) values (5,10) | |
commit | |
select age from table where id > 2 | begin |
commit |
会话2 进行了数据的插入或删除操作,并且提交。
MYSQL中(InnoDB引擎)的四种事务隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read Uncommitted (读未提交) | V | V | V |
Read Committed(读已提交) | X | V | V |
Repeatable Read(可重复读)(默认级别) | X | X | V |
Serializable(串行化) | X | X | X |
InnoDB引擎针保证数据一致性的原理:
1.MVCC
在每一行数据中加上两个隐藏字段,记录行的创建时间和过期时间,但实际存储的是版本号,而不是时间,每开启一个新的事物,版本号加一。
- select:读取创建版本小于等于当前版本的记录,保证在读取之前记录是存在的。
(上述不可重复读的例子,会话2首先进行commit操作,那么被更新过的 age=10这条记录的版本号将会大于 会话1 中的版本号,会话1 将不会查询这条记录) - insert:将当前事务的版本号保存至数据行的创建版本号
- update:新插入一行,以当前事务的版本号作为新行的创建版本号,同时将原记录行的过期版本号设置为当前事务版本号。
- delete:将当前事务的版本号保存至行的删除版本号
2.InnoDB实现MVCC的方式:Consistent Read
首先看两个概念
快照读:读取的是快照版本,也就是历史版本
当前读:读取的是最新版本
普通的SELECT就是快照读,而UPDATE、DELETE、INSERT、SELECT … LOCK IN SHARE MODE、SELECT … FOR UPDATE是当前读。
一致性非锁定读(consistent nonlocking read)是指InnoDB存储引擎通过多版本控制(MVVC)读取当前数据库中行数据的方式。如果读取的行正在执行DELETE或UPDATE操作,这时读取操作不会因此去等待行上锁的释放。相反地,InnoDB会去读取行的一个快照。
一致性非锁定读是InnoDB默认的读取方式,即读取不会占用和等待行上的锁。
事务隔离级别READ COMMITTED和REPEATABLE READ,InnoDB使用一致性非锁定读。
然而对于快照数据的定义却不同:
READ COMMITTED事务隔离级别下,一致性非锁定读总是读取被锁定行的最新一份快照数据。
而在REPEATABLE READ事务隔离级别下,则读取事务开始时的行数据版本。
如果只是有MVCC,并不能保证增删改查一致性:
假设事务A更新表中id=1的记录,
而事务B也更新这条记录,并且B先提交,
如果按照前面MVVC说的,事务A读取id=1的快照版本,那么它看不到B所提交的修改,
此时如果直接更新的话就会覆盖B之前的修改,B的修改就丢失了,这是不允许的。
所以,在修改的时候一定不是快照读,而是当前读。
3.间隙锁
防止其他事务在锁定的区域内插入数据,防止了幻读。
总结
本来只有 Serializable 隔离级别才可以解决幻读问题,
而实际上由于 InnoDB 快照读 的特性使 Repeatable Read 也解决了幻读问题。
当前读 中的幻读问题,innodb默认为它加入了间隙锁,从而避免出现幻读。
使用场景:
-
快照读(snapshot read)
简单的select操作(不包括 select … lock in share mode, select … for update) -
当前读(current read)
select … lock in share mode
select … for update
insert
update
delete
在Repeatable Read(可重复读)(默认级别)下:
快照读 是通过MVVC(多版本控制)实现;
当前读 是通过加record lock(记录锁)和gap lock(间隙锁)来实现的。