MySQL事务
MySQL事务实现原理
MySQL事务究竟是怎么实现的呢?先把结论抛出来:`MySQL的事务机制是基于日志实现的。`为什么是基于日志实现的呢? #### 正常SQL的事务机制 MySQL默认开启事务的自动提交,并且将一条SQL视为一个事务。任意一条写SQL的执行都会记录三个日志:undo-log、redo-log、bin-log。 - `undo-log`:主要记录SQL的撤销日志,比如目前是insert语句,就记录一条delete日志。 - `redo-log`:记录当前SQL归属事务的状态,以及记录修改内容和修改页的位置。 - `bin-log`:记录每条SQL操作日志,只要是用于数据的主从复制与数据恢复/备份。 在写SQL执行记录的三个日志中,bin-log暂且不需要关心,这个跟事务机制没关系,重点是undo-log、redo-log这两个日志,其中最重要的是redo-log这个日志。 >redo-log是一种WAL(Write-ahead logging)预写式日志,在数据发生更改之前会先记录日志,也就是在SQL执行前会先记录一条prepare状态的日志,然后再执行数据的写操作。 但要注意:MySQL是基于磁盘的,但磁盘的写入速度相较内存而言会较慢,因此MySQL-InnoDB引擎中不会直接将数据写入到磁盘文件中,而是会先写到BufferPool缓冲区中,当SQL被成功写入到缓冲区后,紧接着会将redo-log日志中相应的记录改为commit状态,然后再由MySQL刷盘机制去做具体的落盘操作。 因为默认情况下,一条SQL会被当成一个事务,数据写入到缓冲区后,就代表执行成功,因此会自动修改日志记录为commit状态,后续则会由MySQL的后台线程执行刷盘动作。 举个伪逻辑的例子,例如下述这条插入SQL的执行过程大致如下: ```sql -- 先记录一条状态为 prepare 的日志 -- 然后执行SQL,在缓冲区中更改对应的数据 INSERT INTO `zz_users` VALUES(5,"黑竹","男","9999","2022-09-24 23:48:29"); -- 写入缓冲区成功后,将日志记录改为 commit状态 -- 返回 [Affected rows: 1],MySQL后台线程执行刷盘动作 ``` #### 多条SQL的事务机制 先把前面的案例搬下来,如下: ```sql -- 开启事务 start transaction; -- 修改 ID=4 的姓名为:黑熊(原本user_name = 1111) update `zz_users` set `user_name` = "黑熊" where `user_id` = 4; -- 删除 ID=1 的行数据 delete from `zz_users` where `user_id` = 1; -- 提交事务 COMMIT; ``` 比如这段SQL代码执行的过程又是啥样的呢?一起来瞧一瞧: - ①当MySQL执行时,碰到start transaction;的命令时,会将后续所有写操作全部先关闭自动提交机制,也就是后续的所有写操作,不管有没有成功都不会将日志记录修改为commit状态。 - ②先在redo-log中为第一条SQL语句,记录一条prepare状态的日志,然后再生成对应的撤销日志并记录到undo-log中,然后执行SQL,将要写入的数据先更新到缓冲区。 - ③再对第二条SQL语句做相同处理,如果有更多条SQL则逐条依次做相同处理..... - ④直到碰到了rollback、commit命令时,再对前面的所有写SQL做相应处理: 如果是commit提交事务的命令,则先将当前事务中,所有的SQL的redo-log日志改为commit状态,然后由MySQL后台线程做刷盘,将缓冲区中的数据落入磁盘存储。 如果是rollback回滚事务的命令,则在undo-log日志中找到对应的撤销SQL执行,将缓冲区内更新过的数据全部还原,由于缓冲区的数据被还原了,因此后台线程在刷盘时,依旧不会改变磁盘文件中存储的数据。 #### 事务的恢复机制 现在再来思考一个问题,有没有这么一种可能呢?也就是当SQL执行时,数据还没被刷写到磁盘中,结果数据库宕机了,那数据是不是就丢了啊?毕竟本地磁盘中的数据,在MySQL重启后依旧存在,但缓冲区中还未被刷到磁盘的数据呢?因为缓冲区位于内存中,所以里面的数据重启是不会存在的撒? 对于这个问题呢实际上并不需要担心,因为前面聊到过redo-log是一种预写式日志,会先记录日志再去更新缓冲区中的数据,所以就算缓冲区的数据未被刷写到磁盘,在MySQL重启时,依旧可以通过redo-log日志重新恢复未落盘的数据,从而确保数据的持久化特性。 >当然,有人或许又会问:那如果在记录redo-log日志时,MySQL芭比Q了咋整?如果遇到了这个问题呢,首先得恭喜你,你的运气属于很棒,能碰到这个问题的几率足够你买彩票中五百万了~ 玩笑归玩笑,现在回归话题本身,这个问题总不能让它存在是不?毕竟有这个问题对于系统而言也是个隐患啊,但仔细一思考,其实这个问题不必多虑,为啥?推导一下。 首先看看前面的那种情况:数据被更新到缓冲区但没刷盘,然后MySQL宕机了,MySQL会通过日志恢复数据。这里要注意的是:数据被更新到缓冲区代表着SQL执行成功了,此时客户端会收到MySQL返回的写入成功提示,只是没有落盘而言,所以MySQL重启后只需要再次落盘即可。 但如果在记录日志的时候MySQL宕机了,这代表着SQL都没执行成功,SQL没执行成功的话,MySQL也不会向客户端返回任何信息,因为MySQL一直没返回执行结果,因此会导致客户端连接超时,而一般客户端都会有超时补偿机制的,比如会超时后重试,如果MySQL做了热备/灾备,这个重试的时间足够MySQL重启完成了,因此用户的操作依旧不会丢失(对于超时补偿机制,在各大数据库连接池中是有实现的)。 >但如若又有小伙伴纠结:我MySQL也没做热备/灾备这类的方案呐,此时咋整呢? 如果是这样的情况,那就只能自认倒霉了,毕竟MySQL挂了一直不重启,不仅仅当前的SQL会丢失,后续平台上所有的用户操作都会无响应,这属于系统崩溃级别的灾难了,因此只能靠完善系统架构来解决。 #### 事务总结 再次结合undo-log、redo-log日志来看待ACID的四大特性:原子性、一致性、隔离性、持久性。 原子性要求事务中所有操作要么全部成功,要么全部失败,这点是基于undo-log来实现的,因为在该日志中会生成相应的反SQL,执行失败时会利用该日志来回滚所有写入操作。 持久性要求的是所有SQL写入的数据都必须能落入磁盘存储,确保数据不会丢失,这点则是基于redo-log实现的,具体的实现过程在前面事务恢复机制讲过。 隔离性的要求是一个事务不会受到另一个事务的影响,对于这点则是通过锁机制和MVCC机制实现的,只不过MySQL屏蔽了加锁和MVCC的细节。 一致性要求数据库的整体数据变化,只能从一个一致性状态变为另一个一致性状态,其实前面的原子性、持久性、隔离性都是为了确保这点而存在的。
顶部
收展
底部
[TOC]
目录
MySQL 事务的ACID原则
MySQL 事务的隔离机制
MySQL事务实现原理
MySQL之MVCC机制
相关推荐
MySQL教程
MySQL命令
MySQL索引
MySQL锁机制
MySQL版本特性