MySQL是日常开发中使用最广泛的数据库,深入理解它的特性、隔离级别等有助于我们更好地解决实际问题
ACID,分别是原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
原子性是指事务中的所有操作要么全部成功,要么全部失败
一致性是指事务执行前后,数据始终处于一致性状态,不会出现数据丢失
隔离性是指事务提交前的中间状态对其他事务不可见,即相互隔离
持久性是指事务提交后,数据的修改永久保存在数据库中
原子性主要由undo log实现:
undo log是回滚日志,用于记录数据被修改前的信息,它每次都会在一条语句执行时存储一条反方向执行的语句,比如delete一条数据是,undo log中会记录一条insert的语句,主要作用包括提供回滚和MVCC(多版本并发控制),因此通过undo log,当sql语句执行出错时,就可以通过undo log回滚到语句执行之前的状态,保证事务的原子性
持久性主要由redo log实现:
redo log是重做日志,记录的是事务提交是数据页的物理修改,该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log file),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息存储到该日志文件中,用于在刷新脏页到磁盘发生错误时,进行数据恢复使用。因此即使系统发生崩溃,系统也可以从日志中获取记录来恢复数据。
为了更好理解,我们可以看一下如果没有redo log时,要实现持久性要如何实现:
1、从磁盘加载数据到内存
2、在内存中修改数据
3、把新数据持久化到磁盘
但是这样做会有严重的性能问题:
1、InnoDB在磁盘中存储的基本单元是页,如果本次修改只改动一页中的几个字节,但是最终还是要刷新整页的数据,十分浪费资源
2、一个事务可能修改多页中的数据,页之间不是连续的,就会产生随机IO,性能更差
因此我们可以引入redo log来对流程进行修改:
1、从磁盘加载数据到内存
2、在内存中修改数据
3、把新数据写到redo log buffer中
4、把redo log buffer中数据持久化到redo log文件中
5、把redo log文件中数据持久化到数据库磁盘中
而由于redo log采用的是追加的方式记录日志,是顺序读顺序写,因此会比随机读随机写效率高出不少,大大提高了原始策略持久化的效率
具体流程图就是下面这样:
因此,在每次sql语句执行时,会将undo log日志与redo log日志结合起来使用来保证事务的原子性与持久性,大致流程如下:
主要由代码逻辑层面保证,通过完整性约束(主键约束,外键约束)等完成
隔离性主要由MVCC来实现的,在深入学习MVCC之前,一定要说一下事务的隔离级别,而事务的隔离级别是根据不同并发事务产生的问题导致的,因此必须要先说一下并发事务产生的问题:
脏读:一个事务读到其他事务未提交的数据
不可重复读:多次读取相同的数据,得到的结果集不一致,即读到其他事务提交后的数据,针对于数据的内容,主要是读到了其他事务执行update、delete后的数据
幻读:相同的查询条件,多次读取的结果不一致,即读到其他事务提交后的结果,针对于数据的数量,主要是读到了其他事务执行insert后的数据
Read UnCommitted(读未提交):读到其他事务未提交的数据,会出现脏读,不可重复度,幻读
Read Committed(读已提交):读到其他事务已提交的数据,解决了脏读,会出现不可重复读,幻读
Repeatable Read(可重复读):相同的数据,多次读取到的结果集一致,解决了不可重复读,但是还是有可能出现幻读,这里举一个出现了幻读的例子方便理解:
第一次读取的内容与第二次并没有变化,满足可重复读,但是读取内容的数量发生了变化,因此并不能解决幻读
Serializable(串行化):所有事务串行执行,解决了幻读
当前读:读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。我们日常的操作:select ... lock in share mode(共享锁),select ... for update、update、insert、delete(排他锁)都是一种当前读。
快照读:简单的select(不加锁)就是快照读,读取的是记录数据的可见版本
之后我们开始详细介绍一下MVCC(多版本并发控制 Multi-Version Concurrency Control)
MVCC多版本并发控制,会维护一个数据的多个版本,使得读写操作没有冲突,MVCC的具体实现,还需要依赖于数据库中记录的三个隐式字段、undo log日志、readView
回滚日志,在insert、update、delete的时候产生的便于数据回滚的日志。
当insert的时候,产生的undo log只在回滚时需要,事务提交后可以被立即删除;而update、delete的时候,产生的undo log日志不仅在回滚时需要,在快照读时也需要,不会被立即删除。
在数据库执行sql语句时,会根据每条语句生成一条undo log日志,结合数据库的隐藏字段,便可以生成一条版本链:
ReadView(读视图)是快照读SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的(未提交的)id,它其中包含了四个字段:
版本链数据访问规则:
在判断读取哪条数据时,会依照如下的四条判断条件,根据当前undo log的版本号与当前版本的字段进行比较,自上而下,如果遇到合适的条件说明匹配成功:
(1)trx_id == creator_trx_id,说明修改这条数据的事务就是当前事务,所以可见
(2)trx_id < min_trx_id,说明修改这条数据的事务在当前事务生成readView的时候已经提交,所以可见
(3)trx_id 的大小在min_trx_id和max_trx_id之间,此时trx_id如果在m_ids中,说明修改这条数据的事务此时还未提交,所以不可见,相反如果不在m_ids中,说明事务已经提交,可见。
(4)trx_id >= max_trx_id,说明修改这条数据的事务在当前事务生成readView的时候还未启动,所以不可见
生成readView的时机:
RC(Read Commited):在事务中每一次执行快照读时生成ReadView
RR(Repeatable Read):仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView
综上所述,隔离性就是MVCC通过它的隐藏字段等一系列规则解决的,数据库默认的隔离级别是RR(Repeatable Read)
自此,有关于事务的四大特性与他们的底层原理就讲解完毕了,创作不易,如果对你有帮助的话,劳烦点个赞啦,祝好!!!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。