关于mysql:mysql事务提交总结

4次阅读

共计 3846 个字符,预计需要花费 10 分钟才能阅读完成。

两阶段提交

  • 执行器先找引擎取 ID=2 这一行。ID 是主键,引擎间接用树搜寻找到这一行。如果 ID=2 这一行所在的数据页原本就在内存中,就间接返回给执行器;否则,须要先从磁盘读入内存,而后再返回
  • 执行器拿到引擎给的行数据,把这个值加上 1,比方原来是 N,当初就是 N+1,失去新的一行数据,再调用引擎接口写入这行新数据
  • 引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 外面,此时 redo log 处于 prepare 状态。而后告知执行器执行实现了,随时能够提交事务
  • 执行器生成这个操作的 binlog,并把 binlog 写入磁盘
  • 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新实现

解体复原时的判断规定

  • 如果 redo log 外面的事务是残缺的,也就是曾经有了 commit 标识,则间接提交;
  • 如果 redo log 外面的事务只有残缺的 prepare,则判断对应的事务 binlog 是否存在并残缺:

    • a. 如果是,则提交事务;
    • b. 否则,回滚事务。

redo log 和 binlog 是怎么关联起来的?

它们有一个独特的数据字段,叫 XID。解体复原的时候,会按程序扫描 redo log:

  • 如果碰到既有 prepare、又有 commit 的 redo log,就间接提交;
  • 如果碰到只有 parepare、而没有 commit 的 redo log,就拿着 XID 去 binlog 找对应的事务。

因为 redo log 和 binlog 是两个独立的逻辑,如果不必两阶段提交会有什么问题?

  • 先写 redo log 后写 binlog。假如在 redo log 写完,binlog 还没有写完的时候,MySQL 过程异样重启。因为咱们后面说过的,redo log 写完之后,零碎即便解体,依然可能把数据恢复回来,所以复原后这一行 c 的值是 1。然而因为 binlog 没写完就 crash 了,这时候 binlog 外面就没有记录这个语句。因而,之后备份日志的时候,存起来的 binlog 外面就没有这条语句。而后你会发现,如果须要用这个 binlog 来复原长期库的话,因为这个语句的 binlog 失落,这个长期库就会少了这一次更新,复原进去的这一行 c 的值就是 0,与原库的值不同。
  • 先写 binlog 后写 redo log。如果在 binlog 写完之后 crash,因为 redo log 还没写,解体复原当前这个事务有效,所以这一行 c 的值是 0。然而 binlog 外面曾经记录了“把 c 从 0 改成 1”这个日志。所以,在之后用 binlog 来复原的时候就多了一个事务进去,复原进去的这一行 c 的值就是 1,与原库的值不同

为什么 redo log 具备 crash-safe 的能力,而 binlog 没有

redo log 是什么?

一个固定大小,“循环写”的日志文件,记录的是物理日志——“在某个数据页上做了某个批改”。

binlog 是什么?

一个有限大小,“追加写”的日志文件,记录的是逻辑日志——“给 ID=2 这一行的 c 字段加 1”。

redo log 和 binlog 有一个很大的区别就是,一个是循环写,一个是追加写。也就是说 redo log 只会记录未刷盘的日志,曾经刷入磁盘的数据都会从 redo log 这个无限大小的日志文件里删除。binlog 是追加日志,保留的是全量的日志。

当数据库 crash 后,想要复原未刷盘但曾经写入 redo log 和 binlog 的数据到内存时,binlog 是无奈复原的。尽管 binlog 领有全量的日志,但没有一个标记让 innoDB 判断哪些数据曾经刷盘,哪些数据还没有。

举个栗子,binlog 记录了两条日志:

给 ID=2 这一行的 c 字段加 1
给 ID=2 这一行的 c 字段加 1
在记录 1 刷盘后,记录 2 未刷盘时,数据库 crash。重启后,只通过 binlog 数据库无奈判断这两条记录哪条曾经写入磁盘,哪条没有写入磁盘,不论是两条都复原至内存,还是都不复原,对 ID=2 这行数据来说,都不对。

但 redo log 不一样,只有刷入磁盘的数据,都会从 redo log 中抹掉,数据库重启后,间接把 redo log 中的数据都复原至内存就能够了。这就是为什么 redo log 具备 crash-safe 的能力,而 binlog 不具备。

处于 prepare 阶段的 redo log 加上残缺 binlog,重启就能复原,MySQL 为什么要这么设计?

与数据与备份的一致性无关。在时刻 B,也就是 binlog 写完当前 MySQL 产生解体,这时候 binlog 曾经写入了,之后就会被从库(或者用这个 binlog 复原进去的库)应用。所以,在主库上也要提交这个事务。采纳这个策略,主库和备库的数据就保障了一致性。

redolog

当有一条记录须要更新的时候,InnoDB 引擎就会先把记录写到 redo log(粉板)外面,并更新内存,这个时候更新就算实现了。同时,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘外面,而这个更新往往是在零碎比拟闲暇的时候做

步骤

  • InnoDB 的 redo log 是固定大小的,比方能够配置为一组 4 个文件,每个文件的大小是 1GB,那么这块“粉板”总共就能够记录 4GB 的操作。从头开始写,写到开端就又回到结尾循环写
  • write pos 是以后记录的地位,一边写一边后移,写到第 3 号文件开端后就回到 0 号文件结尾。checkpoint 是以后要擦除的地位,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件
  • write pos 和 checkpoint 之间的是“粉板”上还空着的局部,能够用来记录新的操作。如果 write pos 追上 checkpoint,示意“粉板”满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把 checkpoint 推动一下
  • 有了 redo log,InnoDB 就能够保障即便数据库产生异样重启,之前提交的记录都不会失落,这个能力称为 crash-safe

    redolog 的写入机制

  • 为了管制 redo log 的写入策略,InnoDB 提供了 innodb_flush_log_at_trx_commit 参数,它有三种可能取值

    • 设置为 0 的时候,示意每次事务提交时都只是把 redo log 留在 redo log buffer 中 ;
    • 设置为 1 的时候,示意每次事务提交时都将 redo log 间接长久化到磁盘;
    • 设置为 2 的时候,示意每次事务提交时都只是把 redo log 写到 page cache
  • InnoDB 有一个后盾线程,每隔 1 秒,就会把 redo log buffer 中的日志,调用 write 写到文件系统的 page cache,而后调用 fsync 长久化到磁盘

未提交事务是否会写 redolog

未提交事务批改的数据并不会写 redolog

事务提交流程细节

  • 筹备阶段,sql 胜利执行,生成 xid、redolog、undolog,调用 prepare,将事务标记为 trx_started,将 redo log 写盘,这里由 innodb_flush_log_at_trx_commited 参数管制。
  • 提交阶段,引擎执行 prepare 胜利后,开始写 binlog 到 file cache
  • 调用 fsync,将 binlog 从 file cache 写到磁盘,这是由 sync_binlog 参数管制的
  • 告诉引擎 commit,引擎提交后会革除 undolog,并再次将 redo log 写盘,标记事务为 trx_not_started,由后盾线程长久化

双 1 配置

sync_binlog 和 innodb_flush_log_at_trx_commit 都设置成 1。也就是说,一个事务残缺提交前,须要期待两次刷盘,一次是 redo log(prepare 阶段),一次是 binlog。这样最平安

组提交

日志逻辑序列号(log sequence number,LSN)LSN 是枯燥递增的,用来对应 redo log 的一个个写入点。每次写入长度为 length 的 redo log,LSN 的值就会加上 length

有三个并行的事务:trx1、trx2、trx3,事务编号别离为 50、120、160

  • trx1 是第一个达到的,会被选为这组的 leader;等 trx1 – 要开始写盘的时候,这个组外面曾经有了三个事务,这时候 LSN 也变成了 组里事务号最大的 xid;trx1 去写盘的时候,带的就是 LSN=160,因而等 trx1 返回时,所有 LSN 小于等于 160 的 redo log,都曾经被长久化到磁盘;这时候 trx2 和 trx3 就能够间接返回了。所以,一次组提交外面,组员越多,节约磁盘 IOPS 的成果越好。
  • 但如果只有单线程压测,那就只能老老实实地一个事务对应一次长久化操作了。在并发更新场景下,第一个事务写完 redo log buffer 当前,接下来这个 fsync 越晚调用,组员可能越多,节约 IOPS 的成果就越好。
  • 为了让一次 fsync 带的组员更多,MySQL 有一个很乏味的优化:拖时间。

如果你想晋升 binlog 组提交的成果,能够通过设置 binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count 来实现。

  • binlog_group_commit_sync_delay 参数,示意提早多少微秒后才调用 fsync;
  • binlog_group_commit_sync_no_delay_count 参数,示意累积多少次当前才调用 fsync。

这两个条件是或的关系,也就是说只有有一个满足条件就会调用 fsync。所以,当 binlog_group_commit_sync_delay 设置为 0 的时候,binlog_group_commit_sync_no_delay_count 也有效了。

正文完
 0