一、一致性非锁定读
- 一致性的非锁定读是指InnoDB存储引擎通过行多版本控制(multi versioning)的方式来读取当前执行时间数据库中行的数据
原理图解
- 如果读取的行正在执行delete或update操作,这时读取操作不会因此去等待行上锁的释放。相反地,InnoDB存储引擎会去读取行的一个快照数据(Snapshot Data)
- 如下图所示
- 图中直观地展现了InnoDB一致性的非锁定读。之所以称其为非锁定读,因为不需要等到访问的行上X锁的释放
- 快照数据是指该行的之前版本的数据,该实现是通过undo段来实现的。而undo用来在事务中回滚数据,因此快照数据本身是没有额外的开销。此外,读取快照数据是不需要上锁的,因为没有事务需要对历史的数据进行修改操作
- 通过图我们知道,快照数据其实就是当前行数据之前的历史版本,每行记录可能有多个版本,就图中而言,一个行记录可能不止一个快照数据,一般称这种技术为行多版本技术。由此带来的并发控制,称之为多版本并发控制(Multi Version Concurrency Control,MVCC)
不同事务隔离级别下的读取
- 可以看到,非锁定度极大地提高了数据库的并发性。这是InnoDB的默认读取方式,即读取不会占用和等待表上的锁
- 但是在不同事务隔离级别下,读取的方式不同,并不是在每个事务隔离级别下都是采用非锁定的一致性读
- 此外,即使都是使用非锁定的一致性读,但是对于快照数据的定义也不相同
- 在事务隔离级别READ COMMITTED和REPEATABLE READ(innoDB的默认事务隔离级别)下,InnoDB存储引擎使用非锁定的一致性读
- READ COMMITTED:该级别下,非一致性读总是读取被锁定行的最新一份快照数据
- REPEATTABLE READ:该级别下,非一致性读总是读取事务开始时的行数据版本
- 需要注意的是,对于READ COMMITED的事务隔离级别而言,从数据库理论的角度来说,其违反了事务ACID中的I的特性(隔离性)
演示案例
- 创建一个表parent,其中有一个主键a。并向表中插入一条记录
create table parent( id int not null ); insert into parent select 1; select * from parent;
- 在当前MySQL数据库的会话A中执行如下SQL语句开启一个事务,并且事务还未结束:
begin; select * from parent where id=1;
- 此时再开启一个会话B,用来更改表中id为1的数据(加了一个X锁),事务同样也没有提交
begin; update parent set id=3 where id=1;
- 此时回到会话A中,不论采用READ COMMITTED还是REPEATTABLE READ的隔离级别,此时再次读取id为1的行记录,结果都是一样的(因为此时数据库中只有一个行版本的记录,也就是只有一个快照数据)
select * from parent where id=1;
- 现在回到会话B中提交事务
commit;
此时回到会话A中,此时在READ COMMITED和REPEATTABLE READ事务隔离级别下得到的结果就不一样了
- 如果会话A的隔离级别为REPEATTABLE READ:总是读取事务开始时的行数据。因此得到的结果如下:
select @@tx_isolation; select * from parent where id=1;
- 如果会话A的隔离级别为READ COMMITED:它总是读取行的最新版本,如果行被锁定了,则读取该行版本的最新一个快照。因为会话B已经提交了,所以读取到的结果如下
- 总结:下面以时间角度展示了上述的演示过程
二、一致性锁定读
- 在上面的介绍中,InnoDB的默认配置下,事务的隔离级别为REPEATTABLE READ模式,InnoDB的select操作使用一致性非锁定度
- 但是在某些情况下,用户需要显式地对数据库读取操作进行加锁以保证数据逻辑的一致性。而这要求数据库支持加锁语句,即使是对于SELECT的只读操作
- InnoDB对于SELECT语句支持两种一致性的锁定读操作:
- SELECT...FOR UPDATE:对读取的行记录加一个X锁,其他事务不能对已锁定的行加上任何锁
- SELECT...LOCK IN SHARE MODE:对读取的行记录加一个S锁,其他事务可以向被锁定的行加S锁,但是如果加X锁,则会阻塞
- 对于一致性非锁定读,即使读取的行已经被执行了SELECT...FOR UPDATE,也是可以进行读取的,这和之前讨论的情况一样
- 此外,SELECT...FOR UPDATE,SELECT...LOCK IN SHARE MODE必须在一个事务中,当事务提交了,锁也就释放了。因此在使用上述两句SELECT锁定语句时,务必加上BEGIN,STYART TRANSACTION或者SET AUTOCOMMIT=0