vlambda博客
学习文章列表

简析Mysql日志与事务隔离

    接上一篇,本文主要介绍Mysql的日志,隔离级别的实现原理以及如何实现事务。实现事务功能的三个技术,分别是日志文件(redo log 和 undo log),锁技术以及MVCC,然后再讲事务的实现原理,包括原子性是怎么实现的,隔离型是怎么实现的等等

Mysql的日志

undo log

    事务未提交的时候的修改镜像,以便事务回滚修复,保存的是逻辑日志,也就是反向操作语句。

    回滚就是根据回滚日志做逆向操作,比如delete的逆向操作为insert,insert的逆向操作为delete,update的逆向为update等

简析Mysql日志与事务隔离

redo log

    重做日志(redo log)是InnoDB引擎层的日志,用来记录事务操作引起数据的变化,记录的是数据页的物理修改。

    作用:当一条数据需要更新,InnoDB 引擎就会先把记录写到 redo log里面,并更新内存,这个时候更新就算完成了。同时,InnoDB 引擎会在适当空闲的时候,将这个操作记录更新到磁盘里面。其实就是WAL技术,Write-Ahead Logging,它的关键点就是先写日志,再写磁盘,做到快速的数据更新,又不丢失。

     重做日志分为redolog buffer以及重做日志文件redo log。其中redolog buffer在内存,redo log在文件。

简析Mysql日志与事务隔离

    redo log是顺序写入redo log file的物理文件中去的。InnoDB 的 redo log 是固定大小的,比如总大小4GB,设置4组,每组1G,循环写入,循环使用,如上图。

    write pos 是当前记录的位置,一边写一边后移,checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。

    write pos 和 checkpoint 之间的是空着的部分,可以用来记录新的操作。如果 write pos 追上 checkpoint,表示满了,这时候不能再执行新的更新,得停下来先更新到磁盘,把 checkpoint 推进一下。

    redo log是保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe。

bin log

    binlog主要记录原始逻辑,是服务层的日志,还被称为归档日志。binlog可以很方便的对数据进行复制和备份,因而也常用作主从库的同步,不会覆盖对数据覆盖。

日志的两阶段提交

简析Mysql日志与事务隔离

    上图显示,一个数据更新过程中,redolog和binlog的写入情况,其中使用了两阶段提交,这里使用两阶段提交的目的是为了让两份日志之间的逻辑一致。

    redolog 和 binlog 是两个独立的逻辑,如果不用两阶段提交,要么就是先写完 redo log 再写 binlog,或者采用反过来的顺序,都会使两个日志不一致。

    简单说,redo log 和 binlog 都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。

    上述也是MYSQL的ACID中D的实现方法。

事务隔离级别实现原理

事务的实现

    事务根据重做日志,回滚日志以及锁技术来实现,MySql使用不同的锁策略(Locking Strategy)/MVCC来实现四种不同的隔离级别。可重复读、提交读的实现原理跟MVCC有关,未提交读和一致性读跟锁有关

Read view

    InnoDB支持MVCC多版本控制,其中读提交和可重复读隔离级别是利用consistent read view(一致读视图)方式支持的。

    可重复读隔离级别和读提交隔离级别的差别是创建snapshot时机不同。

    可重复读隔离级别是在事务开始时刻,确切的说是第一个读操作创建read view的时候,读提交隔离级别是在语句开始时刻创建read view的。这就意味着可重复读隔离级别下面一个事务的SELECT操作只会获取一个read view,但是读提交隔离级别下一个事务是可以获取多个read view的。

事务ACID的实现原理

原子性的实现

    一个事务必须被视为不可分割的最小工作单位,一个事务中的所有操作要么全部成功提交,要么全部失败回滚,对于一个事务来说不可能只执行其中的部分操作,这就是事务的原子性。就是通过回滚操作UNDO LOG来实现。

持久性的实现

    事务一旦提交,其所做的修改会永久保存到数据库中,此时即使系统崩溃修改的数据也不会丢失。

    先了解一下MySQL的数据存储机制,MySQL的表数据是存放在磁盘上的,为了提升性能InnoDB提供了缓冲池(Buffer Pool),Buffer Pool中包含了磁盘数据页的映射,可以当做缓存来使用:

    现在可能我们的数据已经提交了,但此时是在缓冲池里头,还没来得及在磁盘持久化,所以我们急需一种机制需要存一下已提交事务的数据,为恢复数据使用。于是使用了redo log,其实整个过程中还加上了上节提到日志的两阶段提交。

隔离性的实现

    隔离性是事务ACID特性里最复杂的一个。MySQL隔离级别有以下四种未提交读,提交读,可重复读,连续读

    其实原子性,隔离性,持久性的目的都是为了要做到一致性。

    但隔离型跟其他两个有所区别,原子性和持久性是为了要实现数据的可性保障靠,比如要做到宕机后的恢复,以及错误后的回滚。

    从隔离性的实现可以看出这是一场数据的可靠性与性能之间的权衡,可靠性性高的,并发性能低,可靠性低的,并发性能高;下面说说四种隔离级别使用的方法以及解决的问题。

    未提交读:事务中的修改即使还没提交,对其他事务是可见的。事务可以读取未提交的数据,造成脏读因为读不会加任何锁,所以写操作在读的过程中修改数据,所以会造成脏读。也就是读的操作不能排斥写请求

    提交读:一个事务的修改在他提交之前的所有修改,对其他事务都是不可见的。其他事务能读到已提交的修改变化。具体实现是使用排它锁,读取数据不加锁而是使用了MVCC机制。或者换句话说他采用了读写分离机制。但是该级别会产生不可重读以及幻读问题。

    提交读为什么会产生不可重复读问题呢,这里跟 READ COMMITTED 级别下的MVCC机制有关系,在该隔离级别下每次 select的时候新生成一个版本号,所以每次select的时候读的不是一个副本而是不同的副本。在每次select之间有其他事务更新了我们读取的数据并提交了,那就出现了不可重复读

    可重复读:在一个事务内的多次读取的结果是一样的。这种级别下可以避免,脏读,不可重复读等查询问题。mysql 有两种机制可以达到这种隔离级别的效果,分别是采用读写锁以及MVCC。

    采用读写锁实现,只要没释放读锁,再次读的时候还是可以读到第一次读的数据,这样就无法做到读写并行。

    采用MVCC实现,多次读取只生成一个版本,读到的自然是相同数据。虽然做到了并行,但是实现的复杂度高。在该隔离级别下仍会存在幻读的问题。

    串行读:该隔离级别理解起来最简单,实现也最简单。读加锁,写加锁。

一致性的实现

    一致性就是数据库总是从一个一致性的状态转移到另一个一致性的状态。MYSQL通过回滚,和在并发环境下的隔离做到一致性。