MySQL数据库mysql

3.mysql更新语句执行过程详解

2021-05-30  本文已影响0人  汉江_aa8d

故事是这样开始的,很久很久以前,在一个月结(喝酒的人一个月结一次账)酒店的老板是这样记账的:每次一个客户进来买酒,老板都会找出账本,然后呢再找到这个人,在这个人的名字下面记一下买酒的金额、日期,记完之后把酒给到客户。后来酒店的生意越来越好,老板发现每次客户来都找到账本再找到这个人名字,记录赊账信息,效率十分低下,忙不过来。于是老板想到一个新的办法:他找来一个黑板,每次客户来呢,先把这个人的赊账信息记录在黑板上,等到自己空闲的时候再把赊账信息一条一条更新到账本上。老板这么一搞,一天接待的客户也多了,这真是一个好办法。``

mysql更新语句会涉及到写磁盘的过程,如果每次更新语句都去写磁盘就像酒店老板每次找到账本写赊账信息一样,那必然很影响mysql处理速度,更不可能在现在高并发的场景下满足要求。为了提高处理速度,mysql实际上也是采用了先写黑板再写账本的方法,黑板和账本的配合过程,就是mysql 中常说的WAL(Write-Ahead Logging)技术。其实就是先写日志再写磁盘。这里的日志在mysql中叫redo log,对应的就是酒店老板的小黑板,磁盘对应的就是酒店老板的账本。其实呢mysql 的更新过程不仅有redo log还涉及到binlog,那下面我们先介绍一下 redo log 。

redo log

redo log记录的是物理日志,是对数据页某个位置的修改,所以说redo log 会记修改的数据页的编号(page no)。这里插一句 redo log 也会记录LSN(log sequence number),LSN 是单调递增的,每次写入长度为length的redo log,LSN就会加length,来标识每次redo log 的写入位点,数据页也会记录当前页最后一次修改的LSN,它记录在数据页的头部,它的主要目的是用于在恢复数据时对比redolog日志的LSN号决定是否对该页进行恢复数据,LSN把一个事务开始到恢复的过程串联起来了。前面说的LSN,checkpoint也是有记录的,checkpoint位于redo log file 文件file_header 里面 。innodb 引擎在写redo log 的时候先把redo log 写到 redo log buffer 中(redo log buffer 的大小由),写的时候是一个一个的redo log block ,redo log block每个大小是512字节,其结构如下:

redo buffer 结构.png

其中log block中492字节的部分是log body,该log body的格式分为4部分:

  • redo_log_type:redo log的日志类型,占用1个字节。
  • space:空间的ID,采用压缩的方式后,占用的空间可能小于4字节。
  • page_no:页的偏移量
  • redo_log_body 重做日志的数据部分,恢复时会调用相应的函数进行解析。例如insert语句和delete语句写入redo log的内容是不一样的。

在刷盘的时候,会将 redo log buffer (大小由innodb_log_buffer_size控制)中的日志块写入redo log file 中 就是我们经常在/data 目录中看到的以ib_logfile开头的文件,ib_logfile 文件的大小由innodb_log_file_size 控制,个数由innodb_log_files_in_group 控制,ib_logfile 文件之间的关系是他们是同属于一个组,文件之间通过链表链接 在组内形成一个环,就这样覆盖写,实际上是这样存在的:

redo log物理结构

逻辑上是这样存在的:

redo log 组逻辑结构

这里也要说一下图中 write poscheck point的含义:

如果checkpoint 追上write pos ,那么表示已经没有地方来写日志了,这个时候不能再执行更新,需要将checkpoint 往后移动,移动的部分就是刷脏页(这个过程在下一节讲),有了 redo log,mysql就有了crash_safe 的能力,就是说innodb 能够保证数据库发生异常重启,数据不会丢失。

这里还有一个问题 relog buffer 里面的日志块什么时候写入 redo log 文件呢(既ib_logfile文件)?

这里要注意的是没有提交事务的redo log 也是可能写入到磁盘的,所以说我们分两大类来讨论写盘问题

binlog

binlog 也是日志,它是server 层记录的日志。我们来比较一下它和redo log 日志文件的不同

  1. redo log 是 引擎层产生的,binlog 是由server 层产生的,所有引擎共用
  2. redo log 它是循环写,binlog 是追加写,它不会覆盖数据,写完之后再换到写一个问价写
  3. redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。

binlog 记录有三种格式:statement、row、mixed

  1. statement 记录的是执行语句 主从复制时可能会出现问题
  2. row 记录要修改的数据 缺点就是 日志文件比较大, 优点就是 数据恢复
  3. mixed mysql 会根据执行的每一条具体的 SQL 语句来区分对待记录的日志形式,也就是在 statement 和 row 之间选择一种

binlog 刷盘过程:

binlog 的写入逻辑是这样的:事务执行过程中,先把日志写到 binlog cache,事务提交的时候,再把 binlog cache 写到 binlog 文件中。

一个事务的 binlog 是不能被拆开的,因此不论这个事务多大,也要确保一次性写入。系统给 binlog cache 分配了一片内存,每个线程一个,参数 binlog_cache_size 用于控制单个线程内 binlog cache 所占内存的大小。如果超过了这个参数规定的大小,就要暂存到磁盘。事务提交的时候,执行器把 binlog cache 里的完整事务写入到 binlog 中,并清空 binlog cache。

每个线程有自己 binlog cache,但是共用同一份 binlog 文件。

binlog刷盘的时机是由参数 sync_binlog 控制的

  1. sync_binlog=0 的时候,表示每次提交事务都只 write,不 fsync;

  2. sync_binlog=1 的时候,表示每次提交事务都会执行 fsync;

  3. sync_binlog=N(N>1) 的时候,表示每次提交事务都 write,但累积 N 个事务后才 fsync。因此,在出现 IO 瓶颈的场景里,将 sync_binlog 设置成一个比较大的值,可以提升性能。

在实际的业务场景中,考虑到丢失日志量的可控性,一般不建议将这个参数设成 0,比较常见的是将其设置为 100~1000 中的某个数值。但是,将 sync_binlog 设置为 N,对应的风险是:如果主机发生异常重启,会丢失最近 N 个事务的 binlog 日志。

mysql 更新流程

讲完了 redo log 和 binlog ,现在我们有了基础知识,那我们现在就来看看更新mysql的更新过程是怎样的?

未避免流程差异化太大,这里我们设置一个前提,mysql 的版本是5.7,非自动提交(与自动提交差别不大 主要是为了更清楚的描述整个过程),sync_binlog =1,innodb_flush_log_at_trx_commit = 1,binlog 是打开的,现在我们拿语句UPDATE t set c= 20 where id =2;来说明过程

为什么是两阶段提交

系统重启,数据恢复过程

数据库恢复后会判断redo log的事务是不是完整的,如果不是则根据undo log回滚;如果是完整的并且是prepare状态,则进一步判断对应的事务binlog是不是完整的,如果不完整则一样根据undo log进行回,如果是binlog是完整的就进行提交

上一篇下一篇

猜你喜欢

热点阅读