多版本技术和MVCC
InnoDB 本身就是一个多版本存储引擎(multi-versioned storage engine),当表中的某行被更新后,InnoDB 会将这行的老版本(更新之间的数据)保存到表空间(tablespace)的回滚段(rollback segment),这样做的可以使 InnoDB 支持回滚和并发。
一句话概括:InnoDB 存储引擎可以为每行记录存储多个版本,一般称这种技术为多版本技术。由此带来的并发控制称之为多版本并发控制(Multi Version Concurrency Control)。
MVCC 在读数据中的使用
读分为两种方式:
select * from table;
: 常用的、不加锁的读,它有个名字叫做:一致性非锁定读。select * from table for update
: 使用频率比上面的低一些,这是加锁的读,它也有个名字:一致性锁定读。
一致性非锁定读
InnoDB 的事务是默认开启的,当事务隔离级别是 read committed 或 repeatable read 时,InnoDB 默认使用非锁定一致性读,这种读取方式极大地提高了数据库的并发性。
一致性非锁定读定义:InnoDB 存储引擎通过行多版本控制的方式来读取当前执行时间数据库中的数据。
如上图所示:假设表中有五条记录,第三条记录正在被 A 事务加排他锁,并且还没提交。此时,B 事务也要查询第三条记录,SQL 语句:select * from table
,InnoDB 会去读取第三条记录”最新“ 的快照(老版本),而不是当前正在被加锁的版本。这样就使得事务 B 正常执行,而不是一直卡在这,等待 A 事务完成。从而极大的提高了数据库的并发能力。
关于上面提到的”最新的快照“,这两个隔离级别的定义也是不同的:
- read committed:在这种级别下,一致性非锁定读总是读取被锁定行的最新一份版本。
- repeatable read:在这种级别下,一致性非锁定读总是读取事务开始时行数据版本。
从上面这两个定义也能看出:
-
读提交和可重复读级别的隔离都能够防止脏读,A 事务 update record1 ,但未提交,这相当于 record1 被加了排它锁,B 事务读 record1 时,
select record1 from table
拿到的是 record1 在被 update 之间的快照。所以脏读被防止住了。 -
读提交隔离级别不能防止不可重复读。比如:A 事务先读 record1,B 事务恰好跟新了 record1 并且提交了。(在读提交隔离级别,它总是读取行的最新版本,如果行被锁定了,则读取该行版本的最新一个快照, B事务提交了,那 record1 上的锁就没了), A 事务第二次再读 record1,结果就可能发生变化。所以 读提交防止不了不可重复读。
-
可重复读隔离界别可以防止 不可重复读 和 幻读。在可重复读的级别下,InnoDB 总是在读取事务开始时的行数据。想一下:A 事务 begin 时,record1 肯定是有一个版本的,在 A 事物的执行过程中,无论 record1 被别的事务给更新成了什么样子,innoDB 总是在读取 A 事务开始时的那个版本。
所以无论是 update 修改了记录的属性(不可重复读)还是 insert 、delete 了几条记录(幻读),可重复隔离界别下,一致性非锁定读都能防止住,因为它只读事务开始时的数据版本。
一致性锁定读
一致性锁定读:select * from table for update
是开发者为了保证数据逻辑一致性而手动加的锁。
InnoDB 存储引擎对 select 语句支持两种锁定读的操作:
- 排他锁:
select .... for update
- 共享锁:
select .... lock in share mode
record 如果已被加排他锁,那么其他事务就不能再对它加锁。
record 如果已被加共享锁,那么其他事务可以再给它共享锁,但是不能加排它锁。
(update、insert、delete 默认是加排它锁的)