MySQL的ACID实现原理
AI摘要: 本文介绍了MySQL的ACID实现原理,包括原子性、一致性、隔离性和持久性。Innodb作为MySQL最常用的存储引擎,其内部构造包括buffer pool、redo log和undo log。Atomic实现原理主要通过undo log保证事务的原子性,Consistency由业务逻辑或约束实现,Isolation采用MVCC提高事务并发性能。Durability通过redo log防止数据库崩溃造成数据丢失。
ACID
-
Atomic(原子性)
: 要么全部完成, 要么全部失败(比如转账中,一个人减少余额,另外一个人增加余额,要么全部成功要么全部失败) -
Consisitency(一致性)
:操作前后,数据的状态不能发生变化,需要符合规范,是有效状态(比如资金不能出现负值) -
Isolation(隔离性)
:各个事务并发不能相互影响,需要表现得像是在串行(比如两个人向同一个人转账,收款应该是 ¥,而不是 ¥) -
Durability(持久性)
:数据不能丢失,需要持久化到磁盘中(数据库宕机也不能丢失数据)
Innodb
Innodb
作为MySQL
最常用的存储引擎,其内部构造如下图:
其中最为主要的几个组件为:buffer pool
, redo log
, undo log
Atomic实现原理:undo log
Undo Log是逻辑日志,记录的是数据的增量变化,利用Undo Log可以进行事务回滚,从而保证事务的原子性,同时也实现了多版本并发控制(MVCC),提供了隔离性的基础,解决读写冲突和一致性读问题。这里主要介绍undo Log
实现原子性的过程:
如图所示,当执行SQL
对数据库表增加内容和修改内容,undo log
中就会对应着删除和逆修改的内容,一旦执行失败,将会执行undo log
中的SQL
进行回退。
Consistency实现原理
一致性一般是由业务逻辑或者约束实现的,比如余额不能为负值,在业务代码中校验。
Isolation实现原理:MVCC
当多个事务并发执行时候,会遇到如下三种情况
-
读读:简单的解决方式是使用共享锁
-
写写:简单的解决方式是排它锁
-
读写:简单的解决方式是加锁
加锁是最直接的方法,也是最慢的方法,而MySQL
采用MVCC
来提供事务隔离级别的无锁实现方式,用于提高事务的并发性能
数据库中事务并发和Java中的并发不太一样,数据库中的事务在失败的时候是可以回滚的(Atomic特性),但是Java中是没有,因此不能完全类比
数据库中事务可能出现的隔离级别:
-
脏读:A事务修改了数据,B事务读取到修改的数据,结果A事务因为错误而回滚,B这个时候拿到的数据就是错误的
-
MVCC
解决方案:读已提交 -
行锁解决方案:A事务修改数据但是未提交时候,B事务只能阻塞,虽然可行,但不是最佳的,行锁带来的性能太低,所以
MySQL
还是使用的是MVCC
的无锁机制
-
-
不可重复读:A事务第一次读取到某个数据,B事务紧接着修改了这个数据,A事务第二次读取这个数据,结果发现前后不一致
MVCC
解决方案:可重复读
-
幻读:A事务统计整个表中行做统计操作,B事务在这期间增减行数,导致A事务前后统计结果不一致
MVCC
解决方案:串行化(表锁)
Undo Log
维护的数据结构如下图,每个记录行都存在一个指针指向其上一个历史版本。
Undo Log
在MVCC
中具体实现隔离性的步骤:
-
每个事务都有一个递增的事务ID
-
数据页的行记录中包含了三个隐藏列:
DB_ROW_ID
,DB_TRX_ID(当前操作的事务ID)
,DB_ROLL_PTR(回滚指针,记录当前行的上一个版本)
; -
DB_ROLL_PTR
将数据行的所有快照记录都通过链表的结构串联起来(也就是上图Undo Log的结构)
MVVC
就是通过Read View
在Undo Log
找到对应版本执行CAS
,不同隔离界别采用的Read View
是不同的,这里就懒得写了,内容有点多。
总结,MVCC
的意义:
-
读写互不阻塞
-
降低死锁的概率
-
实现一致性读
Durability实现原理:redo log
为了防止数据库崩溃而造成数据丢失,一种直接的方法是可以在修改数据时候同时写入磁盘,也就是事务提交前页面写入磁盘,但是这种思路会遇到两个问题:
-
随机IO慢:由于修改的数据大概率是不满足局部一致性原理,所以随机IO非常耗时,将会极大降低数据库性能;
-
写放大问题:由于数据库写磁盘不是一条一条写入,而是写入一整个page,所以当修改只发生在很少的数据上,却需要写一整个page,也会降低性能;
综上分析,事务提交前写入磁盘是实现持久化唯一解决办法,但是随机IO又太慢了,那么为什么一定要随机IO呢?为什么一定要把物理数据刷入磁盘呢?直接将操作写入磁盘不好吗?直接顺序IO不好吗?
所以,成熟的解决方案是WAL(Write-ahead logging)
:
MySQL
的每次更新操作都是先写入redo log
中,然后在写入buffer pool
中。redo log
是物理日志,记录的是页面的变化。由于redo log是顺序IO,因此写入速度非常快。如果写入磁盘前发生故障,重启MySQL
后根据redo log
重做即可恢复。