MySQL的ACID实现原理
1 问题背景
前面用sql语句演示了MySQL锁导致的现象,从现象去认知了MySQL锁到底是怎么样的。在本博客MySQL高级系列的文章中也有研究过InnoDB引擎的架构。今天来串联一下InnoDB引擎的架构以及MySQL事务、MySQL锁的知识。
参考自:MySQL事务ACID实现原理
2 事务
如下所示:
start transaction
待执行的若干个sql语句
commit
MySQL默认自动提交事务。start transaction是开启一个事务,接着执行待执行的若干个sql语句,最后使用commit提交。如果没有commit,那么事务就永远不会提交。
3 原子性
概念:原子性是指一个事务是一个不可分割的单位,是一个最小的操作单元,这个操作单元包含若干个操作(一个sql就是一个操作)。这些若干个操作要么全部成功,要么全部失败。
如果一个事务中的一个sql语句执行失败,那么已经执行的sql语句需要执行回滚,回滚到事务执行前的状态。它的实现原理就是undo log。
MySQL中的日志有很多,比如undo log、redo log、bin log、错误日志、慢查询日志、查询日志。InnoDB引擎提供了两种事务的日志,分别是undo log和redo log。
原子性的体现就是体现在回滚上面。如果事务中的某个sql报错会执行回滚。回滚到执行事务之前的状态。因此我们要把执行事务之前的状态记录下来,如果一旦sql语句发生错误,就可以通过执行事务前记录下来的状态信息,回滚到执行事务之前的状态。其实这种原理思想也体现在日常迭代上线的流程中。如果程序上线失败、上线过程中遇到不可知的错误或不能立刻解决的bug,此时需要回滚上线版本,把版本回滚到上一个正常的版本。这与undo log的原理是相通的,就是要记住正确的版本是什么。(用日常迭代上线的例子只是简单解释undo log的思想,日常迭代上线有很多种解决方案,笔者记得有ab发布、蓝绿发布、灰度发布等)。
当事务对数据进行修改的时候,innodb会生成对应的undo log,他会记录每条sql执行的相关信息。如果sql执行失败,innodb会根据undo log的信息执行相反的操作来回滚,比如某一个事务中的某个sql是执行insert操作,那么回滚的时候会执行delete操作。对于delete操作,会执行一个insert操作。对于update操作,会执行相反的update操作。这些相反的操作的执行都基于这个undo log,要把sql的执行信息记录到undo log里面,回滚的时候就根据undo log 去回滚。
4 持久性
概念:持久性是指一个事务一旦提交,它对数据库的改变是永久性的。持久性也是通过一个log来实现,它叫redo log。
4.1 redo log的存在背景
MySQL数据是存在磁盘中的,如果每次读取数据都去磁盘读取则会有一个磁盘IO的过程,那么它的效率会很低。因此InnoDB提供了一个缓存Buffer,该Buffer包含磁盘中部分的数据页的一个映射,作为访问数据库的一个缓冲。当从数据库读取数据,会先从这个Buffer中读取,如果Buffer中没有则会从磁盘中取,读取完才会放在这个Buffer缓存中。当向数据库写入数据的时候,也会先向该Buffer中写入数据,并将Buffer中的数据刷新到磁盘中。但是这时存在一个问题,虽然读写效率提升了,但是如果Buffer中的数据还没来得及同步到磁盘上,此时MySQL宕机了,那么Buffer中的数据就会丢失掉,进而造成数据的丢失。数据丢失了,事务的持久性也就无法保证了。因此redo log就被引入进来解决这个问题。改进后的流程变为:当数据库的数据要进行新增或修改的时候,除了修改Buffer中的数据,还要把这次的操作记录到redo log里面。如果MySQL宕机了,还可以从redo log恢复数据。redo log是一个预写式日志,指它会将所有的修改先写入到日志里面,再更新到Buffer里面(笔者认为此思想与Java更新缓存的方式相通,都是先写库再更新缓存),保证了数据不会丢失。
4.2 为什么将redo log的数据写到磁盘比将Buffer中的数据写到磁盘快?
有两个原因。
Buffer中的数据持久化是随机写的IO。每次修改的数据位置都是随机的。但是redo log是追加模式的,它是在文件的尾部追加,属于一种顺序IO的操作。这种方式就很快。KafKa也是采用顺序IO机制操作的。
Buffer持久化数据是以数据页page为单位的,MySQL默认配置大小为16k,一个数据页上一个小小的数据修改,都需要把整个页的数据写入。而redo log只需要写入真正的部分即可。这个无效的IO就大大减小了。所以redo log比Buffer同步数据要快很多很多。
4.3 redo log什么时候同步到磁盘里去?
redo log没有同步到磁盘之前是在缓存区中,它叫redo log缓冲区。MySQL宕机也没有关系,因为事务没有执行完,即事务没有提交,恢复好数据库后可以通过undo log走回滚操作。redo log的持久化机制可以通过innodb_flush_log_at_trx_commit。
值为0时,表示当事务提交时,并不将缓冲区的redo log写入到磁盘的日志文件,而是等待主线程每秒刷新
值为1时,表示当事务提交时将缓冲区中的redo log 同步写入 到磁盘中, 保证一定会写入成功 。同步写入的速度很快,比Buffer同步快很多。
值为2时,表示事务提交时将缓冲区的redo log 异步写入 到磁盘中。不能保证在commit时一定会将redo log写入磁盘中去。
5 隔离性
5.1 含义
不同事务之间不能相互影响。
5.2 情况
写-写操作,通过锁解决
写-读操作,通过MVCC解决。写读操作会产生幻读、脏读、不可重复读
写-写的情况:
InnoDB的锁大致分为行锁、表锁、间隙锁。若干事务对同一行进行写操作,那么此时只能有一个事务对数据进行操作,那么这个需要由锁来保证。锁能保证在同一时刻只有一个人在操作数据。事务在修改数据之前,这个事务需要获取到相应的锁,获取到之后该事务就可以修改数据了。如果其他事务想要修改事务,那么必须要等到拿到锁的事务提交事务或者回滚后释放锁,下一个事务才能来抢这个锁去执行事务。
写-读操作的情况:
通过四个隔离级别解决。后续会详细讲解并写成博客。
6 总结
原子性:
一个事务内的sql语句要么全部执行,要么全不执行,是通过undo log原理实现的。
一致性:
一致性是指事务执行之后,数据库的完整性约束没有被破坏,事务执行前后都是一个合法的数据状态。他的完整性主要体现在数据库主键要唯一,字段的类型、大小、长度、外键约束要符合要求。一致性是事务追求的最终目标,ACID中三种特性都是为了实现最终的一致性。
隔离性:写-写操作主要是通过锁实现的。如果是读- 写操作则是通过mvcc。
持久性:通过redo log保证的
————————————————
原文链接:https://blog.csdn.net/qq_40634846/article/details/122226371