bin-log & redo-log & undo log
详细分析MySQL事务日志(redo log和undo log)
1. 避免从删库到跑路 - bin log
怎么避免从删库到跑路 – 详解 mysql binlog 的配置与使用
通常对于线上数据库,我们都会定时冷备,dump 导出数据库的全量备份,并且保留一段时间内的所有修改日志,进而实现在必要时回滚到这段时间内的任何一秒,这里的修改日志就是bin log
1.1 什么是bin log
- binlog 即二进制日志,他记录了引起或可能引起数据库改变事件,包括事件发生的时间、开始位置、结束位置等信息,select、show 等查询语句不会引起数据库改变,因此不会被记录在 binlog 中
- 对于事务的执行,只有事务提交时才会一次性写入 binlog,对于非事务操作,则每次语句执行成功后都会直接写入 binlog
- 因此,基于 binlog,我们可以看到每一次对数据库的修改是在何时以何种方式执行的,从而可以实现对任意条操作的回滚,当然
- mysql 的主从同步机制也是依赖 binlog 来实现的,binlog 让从数据库可以精准还原主库的每一个操作
- binlog 是在service层实现的
1.2 写入时机
对于事务的执行,只有事务提交时才会一次性写入 binlog,对于非事务操作,则每次语句执行成功后都会直接写入 binlog
1.3 bin log
结构
binlog
是可以追加写入的,追加写入指的是binlog
文件写到一定大小后会切换到下一个文件,并不会覆盖以前的文件
binlog 的日志格式,有以下三个选项可选
STATEMENT 模式(SBR)
这是mysql binlog 的默认格式,在这个模式下,binlog 只会记录可能引起数据变更的 sql 语句
优点:
- 因为没有记录实际的数据,所以日志量和 IO 都消耗很低,性能是最优的
缺点:
- 有些操作并不是确定的,比如 uuid() 函数会随机产生唯一标识,当依赖 binlog 回放时,该操作生成的数据与原数据必然是不同的,此时可能造成无法预料的后果
- 由于所有的操作都依赖于先后顺序,所以像使用 AUTO_INCREMENT 生成主键 id 的 insert 方法、数据的恢复等都必须串行执行
ROW模式(RBR)
在该模式下,binlog 会记录每次操作的源数据与修改后的目标数据,而不会记录 sql 语句
优点:
- 可以绝对精准的还原,从而保证了数据的安全与可靠
- 复制和数据恢复过程可以是并发进行的
缺点:
- binlog 体积会非常大,同时,对于修改记录多、字段长度大的操作来说,RBR 记录时性能消耗会很严重
- 同时,由于数据是通过二进制方式记录,无法直观的看到 binlog 究竟记录了什么信息
MIXED 模式(MBR)
对上述两种模式的混合使用,对于绝大部分操作,都使用 SBR 来进行 binlog 的记录,只有以下操作使用 RBR 来实现:
- 表的存储引擎为 NDB
- 使用了uuid()、user()、current_user()、found_rows()、row_count()、sysdate() 等不确定函数(now() 函数仍然会以 SBR 方式记录)
- 使用了 insert delay 语句
- 使用了临时表
1.4 如何靠bin log
恢复数据
bin log
会记录所有的逻辑操作,并且采用“追加写”的形式,如果你的DBA承诺说半个月内可以恢复,那么备份系统中一定会保存最近半个月的所有bin-log
,同时系统会定期做整库备份
当需要恢复到指定的某一秒时,比如某天下午两点发现中午12点有一次误删表,需要找回数据,那么可以这么做
- 首先,找到最近的一次去全量备份,如果你运气好,可能就是昨天晚上的一个备份,从这个备份恢复到临时库
- 然后,从备份的时间点开始,将备份的
binlog
一次取出来,重放到中午误删表之前的那个时刻
这样你的临时库就和误删之前的线上库一样了,然后你可以把表数据从临时库中求出来,按需求恢复到线上库中
2. 异常情况下的事务安全 - 重做日志redo log
mysql日志系统之redo log和bin log
mysql 异常情况下的事务安全 – 详解 mysql redolog
2.1 为什么需要redo log
在工程存储项目中,有一个重要的概念,那就是 crash safe,即当服务器突然断电或宕机,需要保证已提交的数据或修改不会丢失,未提交的数据能够自动回滚,这就是 mysql ACID 特性中的一个十分重要的特性 – Atomicity 原子性
依靠 binlog 是无法保证 crash safe 的,因为 binlog 是事务提交时写入的,如果在 binlog 缓存中的数据持久化到硬盘之前宕机或断电
在服务器恢复工作后,由于 binlog 缺失一部分已提交的操作数据,而主数据库中实际上这部分操作已经存在,从数据库因此无法同步这部分操作,从而造成主从数据库数据不一致,这是很严重的
但实际上,innodb 存储引擎是拥有 crash safe 能力的,他是用redo log实现的(redo log是innodb 存储引擎中实现的)
2.2 更新操作是否应该直接操作磁盘数据?
对于每次更新来说,最简单的方法就是每次都把操作记录到磁盘,去磁盘找相应的数据,再进行更新,但这样频繁的IO
操作会导致性能的下降
2.3 WAL
技术
数据库如何用 WAL 保证事务一致性?
再同一事务中,当有记录需要更新时,InnoDB引擎将修改结果更新到内存后,会在redo log
添加一行记录来记录“需要在哪个数据页上做什么修改”,并将该记录的状态置为prepare
,等到commit
提交事务后,会将此次事务中在redo log
添加的记录的状态都置为commit
状态,同时,InnoDB引擎会在适当的时候,将redo log
中状态为commit
的记录的修改更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做
这样的操作叫做Write Ahead Logging
,他的关键在于先写日志,再写磁盘
写日志也是在磁盘上的写操作,为什么比直接在磁盘持久化数据高效?
WAL是顺序写入的,也就是一直在文件末尾append,而持久化数据库的数据是一个随机写入的操作,顺序写会节省大量磁盘悬臂来回寻址的过程,效率更高
现在是否还需要WAL?
现在都用SSD而不在使用HARD,SSD没有机械结构,无需寻道,那么上面所说的优点是否就不存在了?
2.4 redo-log
的结构
InnoDB 的 redo log
是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么这块“粉板”总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写
write pos
是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头checkpoint
是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件write pos
和checkpoint
之间的是“粉板”上还空着的部分,可以用来记录新的操作。如果write pos
追上checkpoint
,表示“粉板”满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把checkpoint
推进一下
2.5 crash-safe
有了 redo log
,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,当数据库发生宕机重启后,可以通过redo log
将未落盘的数据恢复,这个能力称为 crash-safe
2.6 redo log
的写入时机- 两阶段式提交
每条 redolog 都有两个状态 – prepare 与 commit 状态
例如对于一张 mysql 表
(CREATE TABLE `A` (`ID` int(10) unsigned NOT NULL AUTO_INCREMENT, `C` int(10) NOT NULL DEFAULT 0, PRIMARY KEY (`ID`)) ENGINE=InnoDB)
我们执行一条 SQL 语句:
mysql> update T set c=c+1 where ID=2
- 执行器先找引擎取 ID=2 这一行。ID 是主键,引擎直接用树搜索找到这一行。如果 ID=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。
- 执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
- 引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。
- 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。
- 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成
(图中浅色框表示是在InnoDB内部执行的,深色框表示在执行器中执行的)
什么是两阶段式提交
上述步骤将 redo log 的写入拆成了两个步骤:prepare 和 commit,这就是"两阶段提交"
为什么需要两阶段式提交
redo log 和 binlog 都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致
由于 redo log 和 binlog 是两个独立的逻辑,如果不用两阶段提交,要么就是先写完 redo log 再写 binlog,或者采用反过来的顺序。我们看看这两种方式会有什么问题:
mysql> update T set c=c+1 where ID=2
假设当前 ID=2 的行,字段 c 的值是 0,再假设执行 update 语句过程中在写完第一个日志后,第二个日志还没有写完期间发生了 crash,会出现什么情况呢?
- 先写 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,与原库的值不同。
可以看到,如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致
2.7 redo log
如何保证crash safe
在写入 binlog 及事务提交前,innodb 先记录了 redolog,并标记为 prepare 状态,在事务提交后,innodb 会将 redolog 更新为 commit 状态,这样在异常发生时,就可以按照下面两条策略来处理:
- 当异常情况发生时,如果第一次写入 redolog 成功,写入 binlog 失败,MySQL 会当做事务失败直接回滚,保证了后续 redolog 和 binlog 的准确性
- 如果第一次写入 redolog 成功,binlog 也写入成功,当第二次写入 redolog 时候失败了,那数据恢复的过程中,MySQL 判断 redolog 状态为 prepare,且存在对应的 binlog 记录,则会重放事务提交,数据库中会进行相应的修改操作
2.8 bin log
vs redo log
为什么会有两份日志
就像之前说的Mysql的存储引擎是插拔式的,最开始 MySQL 里并没有 InnoDB 引擎,MySQL 自带的引擎是 MyISAM,MyISAM 没有 crash-safe 的能力,binlog 日志只能用于归档,提供恢复数据的能力(注意归档和crash safe的不同)
之后 InnoDB插件形式引入 MySQL ,既然只依靠 binlog
是没有 crash-safe
能力的,所以 InnoDB 使用另外一套日志系统——也就是 redo log
来实现 crash-safe
能力,造成了有两份日志的现状
有了redo log还需要bin log么
需要,Mysql的存储引擎是插拔式的,innoDB实现了redo log,以插件的形式插入Mysql,而MyISAM没有提供redo-log日志,所以bin log还是有用武之地的
有了bin log还需要redo log么(crash safe)
在工程存储项目中,有一个重要的概念,那就是crash safe,即当服务器突然宕机,需要保证已经提交的数据或修改不会丢失,未提交的数据能自动回滚,这就是ACID中一个十分重要的特性 - Atomicity原子性
依靠bin log是无法保证crash safe的,因为binlog是事务提交的时候写入的,如果在bin log者数据持久化到硬盘之前宕机或断电,在服务器恢复工作之后,由于bin log缺失一部分已经提交的操作数据,而主数据库中实际上这部分操作已经存在,从数据库无法同步这一操作导致主从数据库数据不一致,这是很严重的
3. undo log
3.1 什么是undo log
undo log 与 redo log 一起构成了 MySQL 事务日志,并且上文中提到的日志先行原则 WAL 除了包含 redo log 外,也包括 undo log,事务中的每一次修改,innodb 都会先记录对应的 undo log 记录。
- 与 redo log 用于数据的灾后重新提交不同,undo log 主要用于数据修改的回滚。
- 与 redo log 记录的是物理页的修改不同,undo log 记录的是逻辑日志
- 当 delete 一条记录时,undo log 中会记录一条对应的 insert 记录,反之亦然,当 update 一条记录时,它记录一条对应相反的 update 记录,如果 update 的是主键,则是对先删除后插入的两个事件的反向逻辑操作的记录
- 在事务回滚时,我们就可以从 undo log 中反向读取相应的内容,并进行回滚,同时,我们也可以根据 undo log 中记录的日志读取到一条被修改后数据的原值
- 正是依赖 undo log,innodb 实现了 ACID 中的 C – Consistency 即一致性