认识MVCC
MVCC介绍
Multi-Version Concurrency Control,多版本并发控制<br>
MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存
MVCC在MySQL InnoDB中的实现主要是为了提高数据库的并发能力,用更好的方式去处理读写冲突,在有读写冲突时,即使不加锁也能进行非阻塞的并发读操作<br>
说白了MVCC就是为了实现读-写冲突不加锁,而这个读指的就是快照读, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现
当前读与快照读
当前读
像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读
为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
快照读
像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读<br>
之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC;
快照读可能读到的数据并不一定是最新版本的,有可能是之前的历史版本
快照读可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;
与MVCC的关系
准确的说,MVCC多版本并发控制指的是 “维持一个数据的多个版本,使得读写操作没有冲突” 这一个概念;这仅仅是一个理想概念
在MySQL中,快照读就是MySQL实现MVCC理想模型的一个具体非阻塞读功能的落地实现<br>
快照读本身也是一个抽象概念,MVCC模型在MySQL中的具体实现则是由 3个隐式字段,undo日志 ,Read View 等去完成的
数据库并发场景
读-写
存在并发安全问题,会产生数据一致性问题,发生脏读、幻读、不可重复读
写-写<br>
存在并发安全问题,会产生数据一致性问题,数据更新丢失<br>
MVCC带来的好处
多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照数据<br>
在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能<br>
同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决数据更新丢失问题<br>
实现原理
MVCC的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主要是依赖记录中的 3个隐式字段,undo日志 ,Read View 来实现的<br>
三个字段<br>
DB_TRX_ID
6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID<br>
DB_ROW_ID
6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引
DB_ROLL_PTR<br>
7byte,回滚指针,用于配合undo日志,指向上一个旧版本(存储于rollback segment里)
实际还有一个删除flag隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除flag变了
undo log
insert undo log<br>
代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
update undo log<br>
事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除<br>
purge
为了实现InnoDB的MVCC机制,更新或者删除操作都只是设置一下老记录的deleted_bit,并不真正将过时的记录删除。<br>
为了节省磁盘空间,InnoDB有专门的purge线程来清理deleted_bit为true的记录。
为了不影响MVCC的正常工作,purge线程自己也维护了一个read view(这个read view相当于系统中最老活跃事务的read view);
如果某个记录的deleted_bit为true,并且DB_TRX_ID相对于purge线程的read view可见,那么这条记录一定是可以被安全清除的。
Read View
Read View(读视图)是事务进行快照读操作的时候产生的读视图(Read View),在该事务执行快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)<br>
Read View主要是用来做可见性判断的, 即当某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它作为条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。<br>
日志
undo log
undo log是为了实现事务的原子性,是innodb存储引擎级别的日志,用undo Log来实现多版本并发控制(MVCC)<br>
在写数据之前,先将数据备份到undo log中,然后再进行写操作,如果出现了错误或者执行了ROLLBACK,系统可以利用undo log将数据恢复到事务开始之前的状态
可以理解为:当delete时,undo log中会记录对应的insert;当insert时,undo log中会记录对应的delete;当update时,undo log中记录对应相反的update<br>
redo log
redo log是为了实现事务的持久性,是innodb存储引擎级别的日志
当发生数据修改的时候,innodb引擎会先将记录写到redo log中,并更新内存,此时更新就算是完成了,同时innodb引擎会在合适的时机将记录更新到磁盘中<br>
redo log是固定大小的,是循环写的过程; 有了redo log之后,innodb就可以保证即使数据库发生异常重启,之前的记录也不会丢失,叫做crash-safe
redo log的保存分为两个阶段(prepare和commit)
binlog
binlog是MySQL server服务端的日志级别,主要记录mysql功能层面的日志
与redo log的区别
redo log是innodb独有的,binlog是所有引擎都可以使用的<br>
redo log是物理日志,记录的是在某个数据页上做了什么修改;binlog是逻辑日志,记录的是这个语句的原始逻辑<br>
redo log是循环写的,会覆盖旧的信息,空间会用完;binlog是可以追加写的,不会覆盖之前的日志信息
binlog中会记录所有的逻辑,并且采用追加写的方式,一般在企业中数据库会有备份系统,可以定期执行备份,备份的周期可以自己设置<br>
恢复数据的过程:找到最近一次的全量备份数据;从备份的时间点开始,将备份的binlog取出来,重放到要恢复的那个时刻
数据更新的流程<br>
1、执行器先从引擎中找到数据,如果在内存中直接返回,如果不在内存中,查询后返回<br>
2、执行器拿到数据之后会先修改数据,然后调用引擎接口重新吸入数据<br>
3、引擎将数据更新到内存,同时写数据到redo log中,此时处于prepare阶段,并通知执行器执行完成,随时可以操作<br>
4、执行器生成这个操作的binlog<br>
5、执行器调用引擎的事务提交接口,引擎把刚刚写完的redo log改成commit状态,更新完成
若在commit没有发生,redo log和binlog都没有提交成功,保证了数据恢复时的一致性