InnoDB是怎样支持高并发的?
世界是由矛盾组成的,并发的矛盾是什么?线程安全和线程效率,要保证线程安全,就得保证秩序(在处理临界资源的时候),所以难免有些线程需要等待,但这样会降低吞吐量。
为什么Innodb会在当今大行其道?就是因为其有优秀的并发能力。这篇文章就主要谈谈这个问题。
Innodb控制并发的手段有两个:锁 和 MVCC
一 锁
1.普通锁:读写都加锁,本质上是串行的。
2.共享锁和排他锁(Share Locks,记为S;eXclusive Locks,记为X),只有两个共享锁不互斥,也就是说多个读操作是可以并行的,而读写操作是不能并行的。
二 MVCC
MVCC的核心思想是数据的多版本。
当有写操作的是时候,data会可克隆出一个新版本V1,写操作操作的是V1,而读操作将读取老版本V0的数据。
总结:
普通锁:串行
共享锁和排他锁:读读并行
MVCC:读写并行
那老数据是存储在什么地方呢?这里先介绍两个概念。
什么是redo日志?
数据库事务提交以后,不会马上刷新到磁盘上,因为磁盘随机IO性能太低,这样会影响数据库的吞吐量。
优化方案是将写操作先写到redo日志里,这样就变成了顺序IO,再定期刷新到磁盘上,这样就能提高效率了。
假设某个时刻数据库崩溃,有些数据还没有刷新到磁盘上,那么数据库重启后,会重做redo日志里的内容。
什么是undo日志?
数据库事务未提交时,修改前的数据的旧版本会存储在undo日志里,当数据库崩溃或者事务回滚时,会回恢复undo日志里的内容。
总结:
redo日志:保障已提交事务的acid特性
undo日志:保障未提交事务的acid特性
当数据库对数据做修改的时候,需要把数据页从磁盘读到buffer pool中,然后在buffer pool中进行修改,那么这个时候buffer pool中的数据页就与磁盘上的数据页内容不一致,称buffer pool的数据页为dirty page 脏数据,如果这个时候发生非正常的DB服务重启,那么这些数据还没在内存,并没有同步到磁盘文件中(注意,同步到磁盘文件是个随机IO),也就是会发生数据丢失,如果这个时候,能够在有一个文件,当buffer pool 中的data page变更结束后,把相应修改记录记录到这个文件(注意,记录日志是顺序IO),那么当DB服务发生crash的情况,恢复DB的时候,也可以根据这个文件的记录内容,重新应用到磁盘文件,数据保持一致。
什么是回滚段?
回滚段是undo日志的一种组织形式,UNDO内部由多个回滚段组成
现在回到MVCC,旧版本数据存储在什么地方呢?
就是存储在undo日志里。回滚段里存储的就是历史数据的快照,它们不会改变,其他事务可以肆无忌惮地读取这些数据快照。
可见,innodb高并发的实现关键就是拥有多版本并发控制(Multi Version Concurrency Control, MVCC),通过读取旧版本数据(历史数据的快照)来实现并发。
那么select读取的是什么?
select * from t where id>2;
这一句是快照读,不需要加锁。
select * from t where id>2 lock in share mode;
select * from t where id>2 for update;
这两句显式加锁,是非快照读。
以下是《高性能MySQL》中的描述:
MVCC的定义:多版本控制: 指的是一种提高并发的技术。最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅度提高了InnoDB的并发度。
InnoDB是在undo log中实现的,通过undo log可以找回数据的历史版本。找回的数据历史版本可以提供给用户读(按照隔离级别的定义,有些读请求只能看到比较老的数据版本),也可以在回滚的时候覆盖数据页上的数据。在InnoDB内部中,会记录一个全局的活跃读写事务数组,其主要用来判断事务的可见性。
MVCC只在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下工作。其他两个隔离级别够和MVCC不兼容, 因为 READ UNCOMMITTED 总是读取最新的数据行, 而不是符合当前事务版本的数据行。而 SERIALIZABLE 则会对所有读取的行都加锁。
参考:
MySQL-InnoDB-MVCC多版本并发控制
InnoDB并发如此高,原因竟然在这?
说说MySQL中的Redo log Undo log都在干啥