MySQL事务的实现原理(2)

前言: 本小节将围绕Redo Log 、Undo Log 、Bin Log 展开,从某种程度上来说,事务的隔离性是由锁和MVCC机制实现的,原子性和持久性是由Redo Log实现的,一致性是由Undo Log实现的

Redo Log

概念

原子性和持久性是由Redo Log实现的,它确保了事务提交后,事务所设计的所有操作要么全部成功,要么全部失败。

Redo Log主要记录的是物理日志,也就是对磁盘上的数据进行修改操作。
主要包含两部分:

  • 内存中的日志缓冲,易丢失,即 Redo Log Buffer
  • 磁盘上的重做日志文件,不易丢失,即 Redo Log File

相关参数: show variables like ’ %innodb_log%';

基本原理

发生故障时,尽力避免内存中的脏页数据写入数据表IBD文件中,从而使得在重启MySQL服务时,可以根据Redo Log恢复已提交事务但还未写入IBD文件中的数据,实现持久化。
在这里插入图片描述

刷盘规则

在MySQL的InnoDB存储引擎中,通过提交事务时强制执行写日志操作机制实现事务的持久化,会调用一次操作系统的fsync()操作,因为是Log Buffer工作在操作系统的用户空间中,所以持久化到磁盘中的过程需要经过操作系统的内核空间缓冲区 OS Buffer(没有使用O_DIRECT标识位)。
在这里插入图片描述
进行刷盘的几种规则:

  • 开启事务,同时提交事务命令是否刷盘 ,由innodb_flush_log_at_trx_commit决定
  • 每秒刷新一次。由innodb_flush_log_at_timeout的值决定,默认是1s。
  • Log Buffer已使用内存过半
  • 当事务中存在checkPoint。

其中第一条,innodb_flush_log_at_trx_commit变量可取的值有0、1和2,默认为1
结论: 0 最快 其次是2,最后才是1,0与2性能很高,但存在1s的数据崩坏丢失。
在这里插入图片描述

写入机制

Redo Log主要记录的是物理日志,其文件内容是以顺序循环的方式写入的,一个文件写满时会写入另一个文件,最后一个文件写满时,会向第一个文件写数据,并且是覆盖写

  • Wirte Pos是数据表中当前记录所在的位置,随着不断地向数据表中写数据,这个位置会向后移动
  • CheckPoint是当前要擦除的位置,这个位置也是向后移动的,移动到最后一个文件的最后一个位置时
  • Write Pos和CheckPoint之间存在间隔时,中间的间隔表示还可以记录新的操作。如果Write Pos移动的速度较快,追上了CheckPoint,则表示数据已经写满,不能再向Redo Log文件中写数据了
    在这里插入图片描述

Undo Log

概念

MySQL中事务的一致性是由Undo Log实现的。可以进行事务回滚或当数据库崩溃时,对未提交的事务进行回滚操作,在恢复崩溃时先Redo Log数据恢复,后做Undo Log回滚。
Undo Log记录的是逻辑日志,会在事务开始前产生,可以直接当数据库执行一条insert语句时,Undo Log记录一条对应的delete语句。
主要作用:回滚事务和多版本并发事务(MVCC)

存储方式

  • InnoDB存储引擎对于Undo Log的存储采用段的方式进行管理,在InnoDB存储引擎的数据文件中存在一种叫作rollback segment的回滚段,这个回滚段内部有1024个undo log segment段.
  • Undo Log默认存放在共享数据表空间中,默认为ibdata1文件中。如果开启了innodb_file_per_table参数,就会将Undo Log存放在每张数据表的.ibd文件中
  • 默认情况下,InnoDB存储引擎会将回滚段全部写在同一个文件中,也可以通过innodb_undo_tablespaces变量将回滚段平均分配到多个文件中

基本原理

  • Undo Log写入磁盘时和Redo Log一样,默认情况下都需要经过内核空间的OSBuffer
  • MySQL数据库事务提交之前,InnoDB存储引擎会将数据表中修改前的数据保存到Undo Log Buffer,最终Undo Log Buffer中的数据会持久化到磁盘的Undo Log文件中在这里插入图片描述
    在这里插入图片描述

MVCC实现

多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。事务提交之前,会向Undo Log保存事务当前的数据,这些旧版本数据可以作为快照供其他并发事务进行快照读。它的实现原理主要是依赖记录中的 3个隐式字段,undo日志 ,Read View 来实现的

什么是 MySQL InnoDB 下的当前读和快照读?

  • 当前读
    像 select lock in share mode (共享锁), select for update; update; insert; delete (排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁

  • 快照读
    像不加锁的 select 操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即 MVCC ,可以认为 MVCC 是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本

Undo Log的回滚段中,undo logs分为insert undo log和update undo log。

1)insert undo log:事务对插入新记录产生的Undo Log,只在事务回滚时需要,在事务提交后可以立即丢弃。
2)update undo log:事务对记录进行删除和更新操作时产生的Undo Log,不仅在事务回滚时需要,在一致性读时也需要,因此不能随便删除,只有当数据库所使用的快照不涉及该日志记录时,对应的回滚日志才会被purge线程删除

三个point的概念

  • DB_TRX_ID :6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID
  • DB_ROLL_PTR:7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)
  • DB_ROW_ID:6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引

对MVCC有帮助的实质是update undo log ,undo log实际上就是存在rollback segment中旧记录链,它的执行流程如下
在这里插入图片描述

Read View

主要就是维护了一个版本链,然后通过以下几个属性进判断是否可以读取,其中RC每次select生产一次Read View,RR整个事务只生成一次

  • m_ids:还没有提交的事务
  • min_trx_id: 未提交事务中最小的
  • max_trx_id: 已生成的最大事务,不管提没提交
  • creator_trx_id:当前生成该ReadView的事务id
    在这里插入图片描述
    判断规则:
    1) trx_id == creator_trx_id :自己修改的版本,可以访问
    2)trx_id < min_trx_id: 说明已经提交了,可以访问
    3)trx_id > max_trx_id:说明是版本链之后的数据,不可访问
    4)min_trx_id <= trx_id <= max_trx_id: 如果在trx_id 在 m_ids中则不可访问,否则则可以
    在这里插入图片描述

总结: 根据Read View得到的版本链,从上到下,如果处于已提交或就是当前事务数据,则可访问,亦或者,它处于最大与最小trx_id之间,但不属于活跃数组中,则也可访问,其他情况不可访问

BinLog

Redo Log是InnoDB存储引擎特有的日志,MySQL也有其自身的日志,这个日志就是BinLog,即二进制日志

基本概念

BinLog是一种记录所有MySQL数据库表结构变更以及表数据变更的二进制日志。
最重要的使用场景:
1)主从复制:在主数据库上开启BinLog,主数据库把BinLog发送至从数据库,从数据库获取BinLog后通过I/O线程将日志写到中继日志,也就是Relay Log中。然后,通过SQL线程将Relay Log中的数据同步至从数据库,从而达到主从数据库数据的一致性。
2)数据恢复:当MySQL数据库发生故障或者崩溃时,可以通过BinLog进行数据恢复。例如,可以使用mysqlbinlog等工具进行数据恢复

记录模式

BinLog文件中主要有3种记录模式,分别为Row、Statement和Mixed

  • Row模式下的BinLog文件会记录每一行数据被修改的情况,大批量操作会产生大量文件。
  • Statement模式下的BinLog文件会记录每一条修改数据的SQL语句,比如now()等函数就会有问题了。
  • Mixed模式下的BinLog是Row模式和Statement模式的混用

写入机制

binlog的写入逻辑比较简单:事务执行过程中,先把日志写到binlog cache,这个操作会调用write方法,并没有把数据持久化到磁盘,速度比较快;事务提交的时候,再把binlog cache写到binlog文件中,这步会调用fsync将数据持久化到磁盘,速度比较慢。

write和fsync的时机,是由参数sync_binlog控制的:

sync_binlog=0的时候,表示每次提交事务都只write,不fsync。
sync_binlog=1的时候,表示每次提交事务都会执行fsync。
sync_binlog=N(N>1)的时候,表示每次提交事务都write,但累积N个事务后才fsync。
在出现IO瓶颈的场景中,将sync_binlog设置成一个比较大的值,可以提升性能。实际业务场景中,通常设置为100~1000中的某个数值。这样做对应的风险是:如果主机发生一场重启,会丢失最近N个事务的binlog。

BinLog与Redo Log的区别

BinLog和Redo Log在一定程度上都能恢复数据,但是二者有着本质的区别,具体内容如下。
1)BinLog是MySQL本身就拥有的,不管使用何种存储引擎,BinLog都存在,而Redo Log是InnoDB存储引擎特有的,只有InnoDB存储引擎才会输出Redo Log。
2)BinLog是一种逻辑日志,记录的是对数据库的所有修改操作,而Redo Log是一种物理日志,记录的是每个数据页的修改。
3)Redo Log具有幂等性,多次操作的前后状态是一致的,而BinLog不具有幂等性,记录的是所有影响数据库的操作。例如插入一条数据后再将其删除,则RedoLog前后的状态未发生变化,而BinLog就会记录插入操作和删除操作。
4)BinLog开启事务时,会将每次提交的事务一次性写入内存缓冲区,如果未开启事务,则每次成功执行插入、更新和删除语句时,就会将对应的事务信息写入内存缓冲区,而Redo Log是在数据准备修改之前将数据写入缓冲区的Redo Log中,然后在缓冲区中修改数据。而且在提交事务时,先将Redo Log写入缓冲区,写入完成后再提交事务。
5)BinLog只会在事务提交时,一次性写入BinLog,其日志的记录方式与事务的提交顺序有关,并且一个事务的BinLog中间不会插入其他事务的BinLog。而RedoLog记录的是物理页的修改,最后一个提交的事务记录会覆盖之前所有未提交的事务记录,并且一个事务的Redo Log中间会插入其他事务的Redo Log。
6)BinLog是追加写入,写完一个日志文件再写下一个日志文件,不会覆盖使用,而Redo Log是循环写入,日志空间的大小是固定的,会覆盖使用。
7)BinLog一般用于主从复制和数据恢复,并且不具备崩溃自动恢复的能力,而Redo Log是在服务器发生故障后重启MySQL,用于恢复事务已提交但未写入数据表的数据。

两阶段提交

流程图

在这里插入图片描述

前言知识

MySQL为了提升性能,引入了BufferPool缓冲池。查询数据时,先从BufferPool中查询,查询不到则从磁盘加载在BufferPool。
每次对数据的更新,也不总是实时刷新到磁盘,而是先同步到BufferPool中,涉及到的数据页就会变成脏页。
同时会启动后台线程,异步地将脏页刷新到磁盘中,来完成BufferPool与磁盘的数据同步。

为什么要写redo log

防止MysSQL崩溃发生在写binlog后,刷脏前。在主从同步的情况下,从节点会拿到多出来的一条binlog,而主节点没有这条记录,因此binlog是不支持崩溃恢复的

为什么要写两次redo log

先写binlog,再写redo log

当前事务提交后,写入binlog成功,之后主节点崩溃。在主节点重启后,由于没有写入redo log,因此不会恢复该条数据。

而从节点依据binlog在本地回放后,会相对于主节点多出来一条数据,从而产生主从不一致。

先写redo log,再写binlog

当前事务提交后,写入redo log成功,之后主节点崩溃。在主节点重启后,主节点利用redo log进行恢复,就会相对于从节点多出来一条数据,造成主从数据不一致。

两阶段提交原理

MySQL在写入redo log时,会顺便记录XID,即当前事务id。在写入binlog时,也会写入XID。

  • 如果在写入redo log之前崩溃,那么此时redo log与binlog中都没有,是一致的情况,崩溃也无所谓。

  • 如果在写入redo log prepare阶段后立马崩溃,之后会在崩恢复时,由于redo log没有被标记为commit。于是拿着redo log中的XID去binlog中查找,此时肯定是找不到的,那么执行回滚操作。

  • 如果在写入binlog后立马崩溃,在恢复时,由redo log中的XID可以找到对应的binlog,这个时候直接提交即可。

总的来说,在崩溃恢复后,只要redo log不是处于commit阶段,那么就拿着redo log中的XID去binlog中寻找,找得到就提交,否则就回滚。

在这样的机制下,两阶段提交能在崩溃恢复时,能够对提交中断的事务进行补偿,来确保redo log与binlog的数据一致性。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MySQL事务实现的原理主要涉及ACID特性以及锁机制。 ACID是指事务的四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。原子性表示事务的所有操作要么全部成功,要么全部失败回滚;一致性保证了事务执行前后数据库的完整性;隔离性确保并发执行的事务相互之间是隔离的,互不干扰;持久性保证了一旦事务提交,其结果将永久保存在数据库中。 MySQL使用锁机制来实现事务的隔离性。当多个事务同时对数据库进行读写操作时,为了避免数据的不一致性,MySQL会使用不同的锁来保证事务的隔离性。 MySQL提供两种类型的锁:共享锁(S锁)和排他锁(X锁)。共享锁允许事务进行读操作,但不允许进行写操作;排他锁允许事务进行读写操作。当一个事务需要对某个数据进行修改时,它会先申请排他锁,其他事务需要读取该数据时,需等待排他锁释放。 MySQL中还存在多个隔离级别,包括读未提交、读提交、可重复读和串行化。每个隔离级别会对并发事务的读写操作进行不同程度的锁定控制,从而保证事务的隔离性。 总结来说,MySQL事务实现原理是通过ACID特性和锁机制来保证事务的原子性、一致性、隔离性和持久性。锁机制通过共享锁和排他锁来控制对数据的读写访问,不同的隔离级别则决定了锁的粒度和控制方式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值