innodb特性之一致性非锁定读
事务特性
经典的事务,包括ACID特性。
A (Atomicity原子性),一个事务中的所有操作,要么全部完成,要么全部不完成。C(Consistency一致性),在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
I(Isolation隔离性),数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
D(Durability持久性),事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
这篇文章想讨论的主题与I(隔离性)相关。
事务隔离性
隔壁级别可以分为四级。
1、读未提交(Read uncommited):A事务在修改第R行,A事务未提交,B事务能看到A事务修改的结果。这种称之为脏读。
2、读提交(read committed):A事务在修改第R行,由R1修改成R2,A事务未提交,B事务查看第R行,看到的是R1。A事务如果提交,B事务看到的是R2。这种情况称为Phantom,幻读。
3、可重复读(repeatable read):A事务 在修改第R行,由R1修改成R2,A事务未提交,B事务查看第R行,看到是R1。A事务如果提交,B事务看到的还是R1。即是在事务中看到的数据,都是前后一致。
4、串行化(Serializable):即是对同一行数据的读和写操作,都是串行化操作,不能并行,提供最高级别的隔离。
读写如何并发
对于第一级读未提交,很容易出现脏读问题。而默认情况下,innodb使用的是repeated read隔离级别。对于同一行数据的读写操作,即保证了事务之间的隔离,又提供了数据的读写并发访问,innodb使用了MVCC(Multiversion Concurrency Control)多版本并发控制。这里讨论一下如何在保证数据隔离的情况下,保证数据读写还能并发访问。innodb采用的是一致性非锁定读机制。
一致性非锁定读
我们先看一个示例。
对于一个表person,他里面的数据有两行,事务隔离级别可重复读,如下图所示。
开启两个事务A、B。
时间 | Transation A | Transaction B |
---|---|---|
1 | start transaction; | |
2 | select * from person where id = 2; #这里看到的id=2的name值,是"chenhuiyi" |
|
3 | update person set name=‘ABC’ where id = 2; | |
4 | select * from person where id = 2; #这里看到的id=2的name值,还是"chenhuiyi",因为事务的可重复读隔离级别 |
|
5 | select * from person where id = 2; 这里看到的是name为“ABC" |
|
6 | commit | commit |
可以看到,对于同一份数据,事务A进行修改了,事务B还能读到原来和版本,而且可以读写并发,没有阻塞(如果是串行化隔离级别,则是阻塞住了)。这里的MVCC实现原理简单介绍如下:
1、对于事务的写操作,事务会先写下undo日志,undo日志即是记录了对应的行在事务修改前的值,在这里来看,undo日志记录下了id=2所在行的数据, name=‘chenhuiyi’。
2、对于事务的读操作,如果去读某一行数据,如果发现其已经被其他事务上了写锁,并进程操作时,这里由于隔离级别不是串行化,不会尝试对其上读锁,而是会去读该行数据的一个快照数据(snapshot data),快照数据是通过事务的undo数据来生成。因为,在A事务对id=2的行进行写操作的时候,事务B对id=2的行进行读操作,还是没有阻塞,因此,这个一致性锁定读操作,大大增加了innodb的数据读写访问的并发特性。逻辑图所下所示:
3、对于事务中,如果多次修改的行,会存在多种快照数据。如果隔离级别是可重复读的话,则只会读取最初版本的快照。而对于读提交隔离级别的事务,则会读取最新版本的快照。聪明的你,相信动手很容易去验证这个结论。
简单总结
之前一直对innodb的隔离机制,只是停留在使用的水平上,通过把隔离背后的原理搞明白,用起来会更加胸有竹。