数据库通常借助日志来实现事务,如常见的 redo log 和 undo log,都能用来保证事务的原子性和持久性特点。
undo log
undo 日志的作用是把还没有 commit 的事务回滚到事务开始前的状态。如系统崩溃时,可能有些事务还未完成并进行 commit ,这时就可能造成原子性缺失,即事务部分完成。在进行系统恢复时,就要把这些未 commit 的事务借助 undo 日志来回滚。
使用 undo 日志时,要求:
- 记录日志时,对每一步修改操作,记录
(T,x,v)表示事务 T 修改了 x,且修改前的值为 v。我们要借助这条日志来回滚。- 事务提交后,必须在事务的修改持久化完成后,才能写入
COMMIT日志。这样才能保证,COMMIT的日志已经持久化,不需要进行回滚。
事务执行顺序
- 记录
START T- 记录修改
(T, x, v)- 根据事务更新数据库
- 记录
COMMIT T
回滚操作
- 扫描日志,找出所有已经 START, 但是未 COMMIT 的事务。
- 根据 undo 日志,将未 COMMIT 的事务进行回滚。
redo log
redo 是指在进行恢复时,把已经 commit 的事务按照日志重做一遍;对于未完成 commit 的事务,不进行任何操作,直接按 abort 处理。
使用 redo 日志时,要求:
- 记录日志时,对每一步修改操作,记录
(T, x, w)表示事务T修改了x,且修改后的值为w。我们要借助这条日志来重做提交的事务。- 必须在记录
COMMI之后再进行数据持久化。这样可以保证未 COMMIT 的事务,不会对数据库造成任何影响。
事务执行顺序
- 记录
START T- 记录修改
(T, x, w)- 记录
COMMIT T- 根据事务更新数据库
重做操作
- 扫描日志,找出所有已经 COMMIT 的事务。
- 根据 redo 日志,将已 COMMIT 的事务重新执行一遍。
比较
undo 日志与 redo 日志在事务执行顺序,即到底是先记录 commit 还是先更新数据库上有本质的区别,这主要由于是二者的作用以及对于 已经 commit 和未 commit 的事务的操作上的区别。
undo 是把未 commit 的事务进行回滚,某个未完成的事务可能已经部分地修改了数据库的内容,需要回滚;而已完成的事务已经完成了其对数据内容的修改,不需要回滚。因此, COMMIT T 的作用更主要的是表示这个事务不需要进行操作。
redo 是把已经完成 commit 的事务重做一遍(持久化),而直接忽略未完成的事务。如果我们先进行了修改,而事务还未 commit 发生了宕机,即对数据库进行了部分修改,使用 redo 日志重做时,这部分修改会被直接忽略,破解了未提交事务的原子性。而如果先记录日志,事务若能正常记录 commit,在恢复时还可以利用 redo 日志重新完成对数据库的完全修改。
checkpoint
使用日志进行回滚或恢复时,如果从头到尾地完全扫描一遍日志记录,则耗时太久,回滚或重做的工作量太大,可以利用 检查点 的机制来加速回滚。
checkpoint在日志中按照如下的规则记录:
- 日志中记录
checkpoint_start(T1, T2, TN),即记录检查点开启时还未提交的事务。- 等待
(T1,T2,TN)这些所有事务都 COMMIT- 在日志中记录
checkpoint_end。
借助 checkpoint 进行回滚。
- 从后往前扫描日志
- 若先遇到
checkpoint_start, 则将 checkpoint_start 之后未提交的事务进行回滚。- 若先遇到
checkpoint_end, 则将之前一个 checkpoint_start 之后未提交的事务进行回滚。
这里我做了一张图加深理解,纵向表示时间,每一个纵向的条表示一个事务,从左至右从1开始编号。checkpoint end 并不能保证所有事务 都完成提交,只能保证在其对应的 checkpoint start 之前开始的事务完成提交(如1~5);而在那之后开启的事务(6~8) 则由下一次的 checkpoint start 监控。
因此,如果先遇到了 start,则其语句中就包括了当前所有未完成commit 的事务。因此undo只需要从中找到系统崩溃时未完成的事务。
而如果先遇到了 end, 那么在 start 与end 之间可能会有新的事务到来且未提交,但是在 start 之前为完成的事务,在 end 之后,其实都已经完成了。
使用 checkpoint 加速恢复
若要在 redo 日志中使用 checkpoint,要求,在已提交的事务的修改已经进行持久化之后,再在日志中记录 checkpoint_end
- 从后往前,扫描redo log
- 如果先遇到
checkpoint_start, 则把T1~Tn以及checkpoint_start之后的所有已经COMMIT的事务进行重做;- 如果先遇到
checkpoint_end, 则T1~Tn以及前一个checkpoint_start之后所有已经COMMIT的事务进行重做;
对于undo 来讲,先提交记录修改数据库,再记录 checkpoint end;对于redo来讲,先进行数据库持久化,再记录checkpoint end。这样才能保证,之前的checkpoint 的工作已经全部完成,不需要再进行操作。
bin log
binlog 的使用场景主要是主从复制和数据恢复
- 主从复制 :在
Master端开启binlog,然后将binlog发送到各个Slave端,Slave端重放binlog从而达到主从数据一致。 - 数据恢复 :通过使用
mysqlbinlog工具来恢复数据。
主从复制
主从复制主要用途包括:读写分离、主从备份、高可用、架构扩展等方面。
在主从复制中,主服务器负责写,从服务器负责读,采用读写分离的架构,可以提高数据库的服务性能。即使从库的读操作耗时较长,也不会影响主库的更改。
线程
主从复制主要涉及三个线程。其中一个线程运行在 master 节点,另外两个线程运行在 slave 节点。
- master 节点 binlog dump 线程,用于记录binlog。
- slave 节点 IO 线程,用于连接到 master 节点,并从 master 节点读取 binlog 的更新,保存到本地的 relay-log 中。
- slave 节点 SQL 线程,用于读取 relay-log 中的内容,并完成本地数据库的更新。
主从复制过程
- 主数据库更新数据库内容,并将更新写入 binlog。
- slave 的 IO 进程连接到 master,并请求日志文件中从指定位置之后的内容。
- master 收到 IO 请求后,把日志文件的内容返回给 slave 的 IO 进程。
- Slave 收到信息后,将接收到的日志内容一次添加到 slave 的 relay-log 文件的最末端。 同时还要记录此次读取到哪里,以便知道下次从哪里继续读取。
- Slave 的 Sql 进程检测到 relay-log 中新增加了内容后,会马上解析 relay-log 的内容成为在 Master 端真实执行时候的那些可执行的内容,并在自身执行,更新数据库内容。
更新方式
同步复制
master的变化,必须等待所有slave-1,slave-2,…,slave-n完成后才能返回。
异步复制
master只需要完成自己的数据库操作即可。至于slaves是否收到二进制日志,是否完成操作,不用关心, MySQL的默认设置。
半同步复制
master只保证slaves中的一个操作成功,就返回,其他slave不管。