数据库日志

数据库通常借助日志来实现事务,如常见的 redo log 和 undo log,都能用来保证事务的原子性和持久性特点。

undo log

undo 日志的作用是把还没有 commit 的事务回滚到事务开始前的状态。如系统崩溃时,可能有些事务还未完成并进行 commit ,这时就可能造成原子性缺失,即事务部分完成。在进行系统恢复时,就要把这些未 commit 的事务借助 undo 日志来回滚。

使用 undo 日志时,要求:

  1. 记录日志时,对每一步修改操作,记录 (T,x,v) 表示事务 T 修改了 x,且修改前的值为 v。我们要借助这条日志来回滚。
  2. 事务提交后,必须在事务的修改持久化完成后,才能写入 COMMIT 日志。这样才能保证,COMMIT 的日志已经持久化,不需要进行回滚。

事务执行顺序

  1. 记录 START T
  2. 记录修改 (T, x, v)
  3. 根据事务更新数据库
  4. 记录 COMMIT T

回滚操作

  1. 扫描日志,找出所有已经 START, 但是未 COMMIT 的事务。
  2. 根据 undo 日志,将未 COMMIT 的事务进行回滚。

redo log

redo 是指在进行恢复时,把已经 commit 的事务按照日志重做一遍;对于未完成 commit 的事务,不进行任何操作,直接按 abort 处理。

使用 redo 日志时,要求:

  1. 记录日志时,对每一步修改操作,记录 (T, x, w) 表示事务 T 修改了 x,且修改后的值为 w。我们要借助这条日志来重做提交的事务。
  2. 必须在记录COMMI 之后再进行数据持久化。这样可以保证未 COMMIT 的事务,不会对数据库造成任何影响。

事务执行顺序

  1. 记录 START T
  2. 记录修改 (T, x, w)
  3. 记录 COMMIT T
  4. 根据事务更新数据库

重做操作

  1. 扫描日志,找出所有已经 COMMIT 的事务。
  2. 根据 redo 日志,将已 COMMIT 的事务重新执行一遍。

比较

undo 日志与 redo 日志在事务执行顺序,即到底是先记录 commit 还是先更新数据库上有本质的区别,这主要由于是二者的作用以及对于 已经 commit 和未 commit 的事务的操作上的区别。

undo 是把未 commit 的事务进行回滚,某个未完成的事务可能已经部分地修改了数据库的内容,需要回滚;而已完成的事务已经完成了其对数据内容的修改,不需要回滚。因此, COMMIT T 的作用更主要的是表示这个事务不需要进行操作。

redo 是把已经完成 commit 的事务重做一遍(持久化),而直接忽略未完成的事务。如果我们先进行了修改,而事务还未 commit 发生了宕机,即对数据库进行了部分修改,使用 redo 日志重做时,这部分修改会被直接忽略,破解了未提交事务的原子性。而如果先记录日志,事务若能正常记录 commit,在恢复时还可以利用 redo 日志重新完成对数据库的完全修改。

checkpoint

使用日志进行回滚或恢复时,如果从头到尾地完全扫描一遍日志记录,则耗时太久,回滚或重做的工作量太大,可以利用 检查点 的机制来加速回滚。

checkpoint在日志中按照如下的规则记录:

  1. 日志中记录checkpoint_start(T1, T2, TN),即记录检查点开启时还未提交的事务。
  2. 等待(T1,T2,TN) 这些所有事务都 COMMIT
  3. 在日志中记录 checkpoint_end

借助 checkpoint 进行回滚

  1. 从后往前扫描日志
  2. 若先遇到 checkpoint_start, 则将 checkpoint_start 之后未提交的事务进行回滚。
  3. 若先遇到 checkpoint_end, 则将之前一个 checkpoint_start 之后未提交的事务进行回滚。
image-20210908100850153

这里我做了一张图加深理解,纵向表示时间,每一个纵向的条表示一个事务,从左至右从1开始编号。checkpoint end 并不能保证所有事务 都完成提交,只能保证在其对应的 checkpoint start 之前开始的事务完成提交(如1~5);而在那之后开启的事务(6~8) 则由下一次的 checkpoint start 监控。

因此,如果先遇到了 start,则其语句中就包括了当前所有未完成commit 的事务。因此undo只需要从中找到系统崩溃时未完成的事务。

而如果先遇到了 end, 那么在 start 与end 之间可能会有新的事务到来且未提交,但是在 start 之前为完成的事务,在 end 之后,其实都已经完成了。

使用 checkpoint 加速恢复

若要在 redo 日志中使用 checkpoint,要求,在已提交的事务的修改已经进行持久化之后,再在日志中记录 checkpoint_end

  1. 从后往前,扫描redo log
  2. 如果先遇到checkpoint_start, 则把T1~Tn以及checkpoint_start之后的所有已经COMMIT的事务进行重做;
  3. 如果先遇到checkpoint_end, 则T1~Tn以及前一个checkpoint_start之后所有已经COMMIT的事务进行重做;

对于undo 来讲,先提交记录修改数据库,再记录 checkpoint end;对于redo来讲,先进行数据库持久化,再记录checkpoint end。这样才能保证,之前的checkpoint 的工作已经全部完成,不需要再进行操作。

bin log

binlog 的使用场景主要是主从复制和数据恢复

  1. 主从复制 :在 Master端开启 binlog,然后将 binlog发送到各个 Slave端, Slave端重放 binlog从而达到主从数据一致。
  2. 数据恢复 :通过使用 mysqlbinlog工具来恢复数据。

主从复制

主从复制主要用途包括:读写分离、主从备份、高可用、架构扩展等方面。

在主从复制中,主服务器负责写,从服务器负责读,采用读写分离的架构,可以提高数据库的服务性能。即使从库的读操作耗时较长,也不会影响主库的更改。

线程

主从复制主要涉及三个线程。其中一个线程运行在 master 节点,另外两个线程运行在 slave 节点。

  • master 节点 binlog dump 线程,用于记录binlog。
  • slave 节点 IO 线程,用于连接到 master 节点,并从 master 节点读取 binlog 的更新,保存到本地的 relay-log 中。
  • slave 节点 SQL 线程,用于读取 relay-log 中的内容,并完成本地数据库的更新。
主从复制过程
image-20210912163136068
  1. 主数据库更新数据库内容,并将更新写入 binlog。
  2. slave 的 IO 进程连接到 master,并请求日志文件中从指定位置之后的内容。
  3. master 收到 IO 请求后,把日志文件的内容返回给 slave 的 IO 进程。
  4. Slave 收到信息后,将接收到的日志内容一次添加到 slave 的 relay-log 文件的最末端。 同时还要记录此次读取到哪里,以便知道下次从哪里继续读取。
  5. Slave 的 Sql 进程检测到 relay-log 中新增加了内容后,会马上解析 relay-log 的内容成为在 Master 端真实执行时候的那些可执行的内容,并在自身执行,更新数据库内容。
更新方式
  • 同步复制

    master的变化,必须等待所有slave-1,slave-2,…,slave-n完成后才能返回。

  • 异步复制

    master只需要完成自己的数据库操作即可。至于slaves是否收到二进制日志,是否完成操作,不用关心, MySQL的默认设置

  • 半同步复制

    master只保证slaves中的一个操作成功,就返回,其他slave不管。