本文主要讨论MySQLInnoDB引擎下ACID的实现原理。什么是事务、隔离级别的含义等知识可以参考我之前的mysql系列文章。ACID作为关系型数据库,MySQL保证了最常见的InnoDB引擎的ACID。(Atomicity)原子性:事务是最小的执行单位,不允许分割。原子性确保动作要么全部完成,要么完全无效;(Consistency)一致性:事务执行前后,数据保持一致;(Isolation)隔离:在并发访问数据库时,一个事务不受其他事务的干扰。(持久性)持久性:事务提交后。对数据库中数据的更改是持久的,即使数据库发生故障也是如此。隔离隔离主要通过锁和MVCC来实现。这个在我之前的mysql系列文章中也可以看到,这里就不赘述了。MySQL的原子日志有很多种,例如二进制日志、错误日志、查询日志、慢查询日志等。此外,InnoDB存储引擎还提供了两种事务日志:重做日志(redolog)和撤销日志(rollback日志))。其中,redolog用于保证事务的持久化;undolog是事务原子性和隔离性的基础。撤销日志,回滚日志。我们知道MVCC的隔离性是靠它来实现的,原子性也是如此。实现原子性的关键是在事务回滚时能够撤销所有成功执行的SQL语句。当事务修改数据库时,InnoDB会生成相应的undolog;如果事务执行失败或者调用rollback,导致事情回滚,可以利用undolog中的信息将数据回滚到修改前的状态。undolog是逻辑日志,记录了sql执行的相关信息。当发生回滚时,InnoDB会根据undolog的内容做与之前相反的工作:对于每一次insert,在rollback时都会执行delete;对于每一次删除,插入都会在回滚时执行;对于每次更新,滚动时,将执行相反的更新以将数据改回。以update操作为例:事务执行update时,生成的undolog中会包含被修改行的主键(从而知道哪些行被修改了)、哪些列被修改了、这些列修改前后的值。滚动时可以使用此信息将数据恢复到更新前的状态。持久化上面说了Innnodb有很多日志,持久化依赖于重做日志。读取数据:先从缓冲池中读取,如果不在缓冲池中,则从磁盘中读取并放入缓冲池中;写入数据:先写入缓冲池,缓冲池中的数据会周期性同步到磁盘(会出现脏读);所以重做日志就派上用场了。由于redolog也需要存储,所以也涉及到磁盘IO。为什么要使用它?1、redolog的存储是顺序存储,而cache同步是随机操作。2、缓存同步是基于数据页的,每次传输的数据大小大于redolog。重做日志使用WAL(Write-aheadlogging,预写日志记录)。所有修改先写入日志,再更新到BufferPool,保证数据不会因为MySQL宕机而丢失,满足持久化需求。既然redolog在事务提交时也需要将日志写入磁盘,那为什么比直接将BufferPool中的修改数据写入磁盘(即刷脏)更快呢?主要有两个原因:1.刷脏是随机IO,因为每次修改的数据位置是随机的,但是写redolog是append操作,属于顺序IO。2.dirtying的单位是数据页(Page)。MySQL的默认页面大小为16KB。一个Page上的小修改需要写在整个页面上;而redolog只包含需要写入的部分,IO无效。减少很多。redolog和binlog的区别:1、功能不同:redolog用于崩溃恢复,保证MySQL宕机不影响持久化;binlog用于时间点恢复,保证服务器可以根据时间点恢复数据,binlog也用于主从复制。2、层级不同:redolog由InnoDB存储引擎实现,而binlog由MySQLserver层实现(请参考文章前面对MySQL逻辑架构的介绍),同时支持InnoDB等存储引擎同时。3、内容不同:重做日志是物理日志,内容以磁盘的页为单位;binlog的内容是二进制的。根据binlog_format参数的不同,它可能基于SQL语句,基于数据本身,或者两者的混合。4、写入时机不同:binlog是在事务提交的时候写入的;redolog的写入时机比较多样:默认的flushing策略:当事务提交时,会调用fsync来flushredolog;修改innodb_flush_log_at_trx_commit参数可以改变这个策略,但是不会保证事务的持久性。其他刷盘时机:比如master线程每秒刷一次redolog等,这样做的好处是不用等到commit的时候才刷盘,commit速度大大提高加速。让我们看看SQL更新语句是如何工作的:例如:updatetsetage=20whereid=1;持久化肯定和写有关,MySQL使用了WAL技术,WAL的全称是Write-AheadLogging,它的要点是先写日志,再写到磁盘。【重做日志】重做日志就是这个日志。当有记录需要更新时,InnoDB引擎会先将记录写入redolog(并更新内存),此时更新完成。在适当的时候(上面提到的刷盘时机),将这条操作记录更新到磁盘中。redolog有两个特点:固定大小,循环writecrash-safe(只要有数据flush到磁盘,就会从redolog中保存。log,数据库重启后,可以恢复所有redolog中的数据直接写入内存(这就是redolog具有crash-safe能力的原因)redolog有两个阶段:commit和prepare。使用“两阶段提交”,数据库的状态可能与使用其日志恢复的库的状态不一致。[BufferPool]BufferPool包含一些数据页在磁盘中的映射,作为访问数据库的缓冲:当读取数据时,会先从BufferPool中读取。如果没有BufferPool,则从磁盘中读取,放入BufferPool;向数据库写入数据时,会先写入BufferPool,BufferPool中修改后的数据会周期性的刷新到磁盘。BufferPool的使用大大提高了读写数据的效率,但是也带来了新的问题:如果MySQL宕机了,此时在BufferPool中修改的数据还没有刷新到磁盘,就会造成数据丢失,并且不保证事务持久性。所以添加了redolog。修改数据时,除了修改BufferPool中的数据外,还会将操作记录到redolog中;当事务提交时,会调用fsync接口刷新redolog。如果MySQL宕机了,可以在重启的时候读取redolog中的数据来恢复数据库。一致性一致性是事务追求的最终目标。实现一致性的措施包括:保证原子性、持久性和隔离性。如果不能保证这些特性,就不能保证事务的一致性。数据库本身提供保证,比如不允许在整数列中插入字符串值,字符串的长度不能超过列的限制。例如,如果转账操作只扣除转账方的余额,而没有增加接收方的余额,那么无论数据库多么完善,也无法保证状态的一致性。CAP与ACID的一致性CAP定理中的数据一致性实际上是指分布式系统中的每个节点对于相同数据的副本具有相同的值;而ACID中的一致性是指数据库中如果schema规定了一个值必须是唯一的,那么一个一致性的系统就必须保证这个值在所有的操作中都是唯一的。从这个角度来看,CAP和ACID在一致性定义上有着根本的区别。总结事务的ACID的四个基本特性是保证数据库运行的基石,但是充分保证数据库的ACID,尤其是隔离会对性能有比较大的影响,在实际使用中我们也会根据对业务需求Isolation进行调整。除了隔离之外,数据库的原子性和持久性被认为是比较容易理解的特性。前者确保数据库中的所有事务都执行或根本不执行,后者确保对数据库的所有写入都被持久化存储。它是非易失性的,一致性不仅是数据库对自身数据完整性的要求,也是对开发者的要求——编写逻辑上正确合理的事务。最后,也是最重要的,当其他人谈论一致性时,他们必须了解他们的背景。
