一文带你看通透,MySQL 事务 ACID 四大特性实现原理
hello,大家好,我是张张,「架构精进之路」公号作者。
说起 MySQL 事务处理的四大特性,相信大家都可以张口就来:ACID!
那 MySQL 是如何实现 ACID 的?每种特性的原理又是如何实现的?
今天,本文笔者主要探讨 MYSQL InnoDB 引擎下的 ACID 实现原理,对事务、锁以及隔离级别等内容统一进行回顾一下。
1、ACID 特性
原子性(Atomicity)
单个事务,为一个不可分割的最小工作单元,整个事务中的所有操作要么全部 commit 成功,要么全部失败 rollback,对于一个事务来说,不可能只执行其中的一部分 SQL 操作,这就是事务的原子性。
一致性(Consistency)
数据库总是从一个一致性的状态转换到另外一个一致性的状态。在前面的例子中, 一致性确保了,即使在执行第三、四条语句之间时系统崩溃,信用卡账户也不会损 失 100 块,因为事务最终没有提交,所以事务中所做的修改也不会保存到数据库中,保证数据一致性。
隔离性(Isolation)
通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见(隔离)的。避免多个事务并发执行的时候不会互相干扰。
持久性(Durability)
一旦事务提交,则其所做的修改就会永久保存到数据库中,之后的其他操作或故障都不会对事务的结果产生影响。
2、ACID 具体实现
原子性:通过 undolog 来实现。
持久性:通过 binlog、redolog 来实现。
隔离性:通过(读写锁+MVCC)来实现。
一致性:MySQL 通过原子性、持久性、隔离性最终实现数据一致性。
对 MySQL 来说,逻辑备份日志(binlog)、重做日志(redolog)、回滚日志(undolog)、锁技术 + MVCC 就是 MySQL 实现事务的基础。
2.1 原子性原理
事务通常是以 BEGIN TRANSACTION 开始,以 COMMIT 或 ROLLBACK 结束。
COMMIT 表示提交,即提交事务的所有操作并持久化到数据库中。
ROLLBACK 表示回滚,即在事务中运行的过程中发生了某种故障,事务不能继续执行,系统将事务中对数据库所有已完成的操作全部撤销,回滚到事务开始时的状态,这里的操作指对数据库的更新操作(查询操作忽略)。这时候需要用到 undolog 来进行回滚。
undolog:
每条数据变更(INSERT/UPDATE/DELETE/REPLACE)等操作都会生成一条 undolog 记录,在 SQL 执行前先于数据持久化到磁盘。
insert 语句,回滚时会执行 delete;
delete 语句,回滚时会执行 insert;
update 语句,回滚时便执行相反的 update,把数据改回来。
当事务需要回滚时,MySQL 会根据回滚日志对事务中已执行的 SQL 做逆向操作,比如 DELETE 一行数据的逆向操作就是再把这行数据 INSERT 回去,其他操作同理。
undolog 记录事务开始前老版本数据,用于实现回滚,保证原子性,实现 MVCC,会将数据修改前的旧版本保存在 undolog,然后行记录有个隐藏字段回滚指针指向老版本。
2.2 持久性原理
我们知道,MySQL 表数据是持久化到磁盘中的,但如果所有操作都去操作磁盘,等并发上来了,那处理效率无法保证,因此引入了缓冲池(Buffer Pool)的概念,Buffer Pool 中包含了磁盘中部分数据页的映射,可以当做缓存来用;这样当修改表数据时,我们把操作记录先写到 Buffer Pool 中,并标记事务已完成,等 MySQL 空闲时,再把更新操作持久化到磁盘里,从而大大缓解了 MySQL 并发压力。
MYSQL 的持久性便是由 redo log 来保证。
redo log
是一种物理日志,作用:会记录事务开启后对数据做的修改,crash-safe。
它类似于一个卸货的小推车,我们若是每卸一件物品就拿着去入库,那岂不是特浪费时间,若有一个小推车,我们将货物首先存放在小推车,当推车满了再往库里存,可以大大提升效率。
其实就是 MySQL 里经常说到的 WAL 技术,WAL 的全称是 Write-Ahead Logging,它的关键点就是先写日志,再写磁盘,也就是先装小推车,等不忙的时候再装库。
特性:空间一定,写完后会循环写,有两个指针 write pos 指向当前记录位置,checkpoint 指向将擦除的位置,redolog 相当于是个取货小车,货物太多时来不及一件一件入库太慢了这样,就先将货物放入小车,等到货物不多或则小车满了或则店里空闲时再将小车货物送到库房。用于 crash-safe,数据库异常断电等情况可用 redo log 恢复。
以下只作了解:
写入流程:先写 redo log buffer,然后 wite 到文件系统的 page cache,此时并没有持久化,然后 fsync 持久化到磁盘
写入策略:根据 innodb_flush_log_at_trx_commit 参数控制(innodb 以事务的什么提交方式刷新日志)
0——>事务提交时只把 redo log 留在 redo log buffer
1——>将 redo log 直接持久化到磁盘(所以有个双“1”配置,后面会讲)
2——>只是把 redo log 写到 page cache
2.3 隔离性原理
MYSQL 有四种隔离级别,用来解决存在的并发问题:脏读、幻读、不可重复读。
那么不同隔离级别,隔离性是怎样实现的呢?
一句话:锁+MVCC。
锁
表锁:读锁(不会阻塞其他线程的读操作,阻塞写操作);写锁(读写操作都阻塞)
行锁:需要的时候加上,并不是马上释放,等事务提交才释放,两阶段锁协议
锁的类型
间隙锁-gap lock:锁定区间范围,防止幻读,左开右开,只在可重复读隔离级别下生效—|—为了阻止多个事务将记录插入到同一范围内,而这会导致幻读问题的产生
记录锁-record Lock:锁定行记录,索的索引,索引失效,为表锁
临键锁-next-key Lock:record lock+gap lock 左开右闭(解决幻读)
锁的模式
select .... for update (持有写锁,别的不可加读锁,也不可加写锁)
select .... lock in share mode(持有读锁,别的可以再加读锁,不可加写锁)
共享锁-读锁-S 锁
排他锁-写锁-X 锁
意向锁:读意向锁+写意向锁
自增锁
全局锁:全库逻辑备份
死锁:两个或多个事务在同一资源上相互占用,并请求加锁时,造成相互等待,无限阻塞
MVCC:实现多版本并发控制,实现原理:使用版本链+Read View
读已提交和可重复读实现原理就是 MVCC Read View 不同的生成时机。可重复读只在事务开始时生成一个 Read View,之后都用的这个;读已提交每次执行前都会生成 Read View。
2.4 一致性原理
一致性是事务追求的最终目标,前文所述的原子性、持久性和隔离性,其实都是为了保证数据库状态的一致性,数据库中的增删改操作,使数据库不断从一个一致性的状态转移到另一个一致性的状态。
总结
事务该回滚的回滚,该提交的提交,提交后该持久化磁盘的持久化磁盘,该写缓冲池的写缓冲池+写日志。
对于数据可见性,通过四种隔离级别进行控制,使得库表中的有效数据范围可控,保证业务数据的正确性的前提下,进而提高并发程度,支撑服务高 QPS 的稳定运行,保证数据的一致性,这就是咱们说个不停的 MySQL 数据库事务 ACID 四大特性。
希望今天的讲解对大家有所帮助,谢谢!
Thanks for reading!
作者:架构精进之路,十年研发风雨路,大厂架构师,CSDN 博客专家,专注架构技术沉淀学习及分享,职业与认知升级,坚持分享接地气儿的干货文章,期待与你一起成长。
关注并私信我回复“01”,送你一份程序员成长进阶大礼包,欢迎勾搭。
版权声明: 本文为 InfoQ 作者【架构精进之路】的原创文章。
原文链接:【http://xie.infoq.cn/article/217aca5062736505d051bd6f1】。文章转载请联系作者。
评论