文章目录
- 一、简介
- 二、redo 日志
- 2.1 简介
- 2.2 工作原理
- 2.3 刷盘策略
- 2.4 相关参数
- 三、undo 日志
- 3.1 简介
- 3.2 回滚段
- 3.3 详细工作流程
- 3.4 日志清理
一、简介
- 事务四种特性的底层实现机制
- 事务的隔离性由
锁机制
实现。 - 而事务的原子性、一致性和持久性由事务的 redo 日志和 undo 日志来保证。
- redo log 称为
重做日志
,提供再写入操作,恢复提交事务修改的页操作,用来保证事务的持久性。 - undo log 称为
回滚日志
,回滚行记录到某个特定版本,用来保证事务的原子性、一致性。
- redo 和 undo 都可以视为是一种
恢复操作
,但 undo 并不是 redo 的逆过程
-
redo log
:是存储引擎层(innodb)生成的日志,记录的是物理级别
上的页修改操作,比如页号xxx,偏移量yyy,写了zzz数据,主要为了保证数据的可靠性。 -
undo log
:是存储引擎(innodb)生成的日志,记录的是逻辑操作
的日志。比如对某一行数据进行了insert语句操作,那么undo log就记录一条与之相反的delete操作。主要用于事务的回滚
(undo log 记录的是每个修改操作的 逆日志)和 一致性非锁定读(回滚行记录到某种特定版本,即多版本并发控制)。
二、redo 日志
2.1 简介
- InnoDB 修改数据的基本流程
- 当我们想要修改DB上某一行数据的时候,InnoDB是把
数据从磁盘读取到内存
的缓冲池上进行修改。 - 这个时候数据在内存中被修改,与磁盘中相比就存在了差异,我们称这种有差异的数据为
脏页
。 - InnoDB对脏页的处理
不是每次生成脏页就将脏页刷新回磁盘
,这样会产生海量的IO操作,严重影响InnoDB的处理性能。 - 既然脏页与磁盘中的数据存在差异,那么如果在这期间DB出现故障就会造成
数据的丢失
。 - 为了解决这个问题,
redo log
就应运而生了。
2.2 工作原理
innoDB 引擎的事务采用了
WAL技术
( write-ahead logging ),这种技术的思想就是先写日志,再写磁盘,只有日志写入成功,才算事务提交成功,这里的日志就是 redo log。当发生宕机且数据未刷到磁盘的时候,可以通过 redo log 来恢复,保证ACID 中的 D(持久性),这就是 redo 的作用。
- 先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝。
- 生成一条重做日志并写入redo log buffer,记录的是数据被修改后的值。
- 当事务commit时,将redo log buffer中的内容刷新到 redo log file,对 redo log file采用追加写的方式。
- 定期将内存中修改的数据刷新到磁盘中。
2.3 刷盘策略
redo log 的写入并不是直接写入磁盘的,InnoDB 引擎会在写 redo log 的时候先写 redo log buffer,之后以
一定的频率
刷入到真正的 redo log file 中。
innodb_flush_log_at_trx_commit
参数值 | 说明 |
0 | 表示每次事务提交时不进行刷盘操作。(系统默认的 master thread 每隔 1s 进行一次重做日志的同步) |
1 | 表示每次事务提交时都将进行同步,刷盘操作( |
2 | 表示每次事务提交时都只把 redo log buffer 内容写入 page cache,不进行同步。由 os 自己决定什么时候同步到磁盘文件。 |
innodb_flush_log_at_trx_commit = 1
2.4 相关参数
-
innodb_log_buffer_size
:redo log buffer 大小,默认 16M ,最大值是 4096M,最小值为 1M。
mysql> show variables like '%innodb_log_buffer_size%';
+------------------------+----------+
| Variable_name | Value |
+------------------------+----------+
| innodb_log_buffer_size | 16777216 |
+------------------------+----------+
1 row in set (0.00 sec)
mysql>
innodb_log_group_home_dir
- 指定 redo log 文件组所在的路径,默认值为
./
,表示在数据库的数据目录下。 - MySQL的默认数据目录( var/lib/mysql )下默认有两个名为
ib_logfile0
和ib_logfile1
的文件,log buffer中的日志默认情况下就是刷新到这两个磁盘文件中。
mysql> show variables like '%innodb_log_group_home_dir%';
+---------------------------+-------+
| Variable_name | Value |
+---------------------------+-------+
| innodb_log_group_home_dir | ./ |
+---------------------------+-------+
1 row in set (0.01 sec)
mysql>
-
innodb_log_files_in_group
:指明redo log file的个数,命名方式如:ib_logfile0,iblogfile1… iblogfilen。默认2个,最大100个。
mysql> show variables like '%innodb_log_files_in_group%';
+---------------------------+-------+
| Variable_name | Value |
+---------------------------+-------+
| innodb_log_files_in_group | 2 |
+---------------------------+-------+
1 row in set (0.01 sec)
mysql>
innodb_log_file_size
- 单个 redo log 文件设置大小,默认值为
48M
,最大值为512G。 - 注意最大值指的是整个 redo log 系列文件之和,即(innodb_log_files_in_group * innodb_log_file_size )不能大于最大值512G。
mysql> show variables like '%innodb_log_file_size%';
+----------------------+----------+
| Variable_name | Value |
+----------------------+----------+
| innodb_log_file_size | 50331648 |
+----------------------+----------+
1 row in set (0.00 sec)
mysql>
三、undo 日志
undo log 是事务原子性的保证,在事务中
更新数据的前置操作
其实是要先写入一个 undo log。
3.1 简介
undo日志的作用:1. 回滚数据;2. MVCC
- 事务需要保证
原子性
,也就是事务中的操作要么全部完成,要么什么也不做。 - 有时候事务执行到一半,可能会遇到服务器本身的错误、操作系统错误、突然断电导致的错误或者执行者手动 ROLLBACK。
- 以上情况出现,需要把数据改回原先的样子,这个过程称之为 回滚,这样就可以造成一个假象:这个事务看起来什么都没做,所以符合
原子性
要求。
场景 | 操作 |
插入 | 至少要把这条记录的主键值记下来,之后回滚的时候需要把这个主键值对应的 |
删除 | 至少要把这条记录的内容都记下来,这样之后回滚再把这些内容组成一条新记录 |
更新 | 至少要把修改过这条记录前的旧值都记录下来,这样之后回滚时再把这条记录 |
查询 | 并不会修改任何用户记录,并 |
- 另外,If another transaction needs to see the original data as part of a consistent read operation, the unmodified data is retrieved from undo log records (This will be explained in more detail in the following sections MVCC).
3.2 回滚段
- 简介
- InnoDB 对 undo log 的管理采用段的方式,也就是
回滚段(rollback segment)
。 - 每个回滚段记录了 1024 个 undo log segment,InnoDB 支持最大 128 个 rollback segment,故其支持同时在线的事务为 128 * 1024 个。
- 当开启一个事务需要写 undo log 的时候,需要先在 undo log segment 中找到一个空闲的位置再去申请 undo 页,然后在申请到的页中进行 undo log 的写入。
- 回滚段中的数据分类
-
未提交的回滚数据(uncommitted undo information)
:该数据所关联的事务并未提交,用于实现读一致性,所以该数据不能被其它事务的数据覆盖; -
已经提交但未过期的回滚数据(committed undo information)
:该数据关联的数据已经提交,但是仍受到 undo retention 参数的保持时间的影响; -
事务已经提交并过期的数据(expired undo information)
:事务已经提交,而且数据保存时间已经超过 undo retention 参数指定的时间,属于已经过期的数据。当回滚段满了之后,会优先覆盖此部分数据。
事务提交后并不能马上删除 undo log 及 undo log 所在的页。这是因为可能还有其它事务需要通过 undo log 来得到行记录之前的版本;故事务提交时将 undo log 放入一个链表中,是否可以最终删除 undo log 及 undo log 所在的页由 purge 线程来判断(后续会讲到)。
3.3 详细工作流程
- 隐藏列:对于 InnoDB 引擎来说,每个行记录除了记录本身的数据之外,还有几个隐藏的列
列 | 说明 |
DB_ROW_ID | 如果没有为表显示的定义主键,并且表中也没有定义唯一索引,那么 InnoDB 会自动为表添加一个 row_id 的隐藏列作为主键。 |
DB_TRX_ID | 每个事务都会分配一个事务 ID,当对某条记录发生变更时,就会将这个事务的事务 ID 写入 trx_id 中。 |
DB_ROLL_PTR | 回滚指针,本质上就是指向 undo log 的指针。 |
- 执行事务
- INSERT:插入的数据都会生成一条 insert undo log,并且数据的回滚指针会指向它。undo log 会记录 undo log 的序号、插入主键的列和值;在进行 rollback 的时候,通过主键直接把对应的数据删除即可。
BEGIN;
INSERT INTO user (name) VALUES ("tom");
- UPDATE:对于更新的操作会产生 update undo log,并且会分更新主键和不更新主键
# 不更新主键时会把老的记录写入新的 undo log,让回滚指针指向新的 undo log
# 它的 undo no 会 +1,并且新的 undo log 会指向老的 undo log
UPDATE user SET name = "Sun" WHERE id = 1;
# 对于更新主键的操作,会先把原来的数据 deletemark 标识打开,这是并没有真正的删除数据,真正的删除会交给清理线程去判断
# 然后在后面插入一条新的数据,新的数据也会产生 undo log
UPDATE user SET id = 2 WHERE id = 1;
- 回滚事务
- 通过 undo no = 3 的日志把 id = 2 的数据删除;
- 通过 undo no = 2 的日志把 id = 1 的数据的 deletemark 还原成 0;
- 通过 undo no = 1 的日志把 id = 1 的数据的 name 还原成 Tom;
- 通过 undo no = 0 的日志把 id = 1 的数据删除。
3.4 日志清理
- undo 的类型
- insert undo log 是指在 insert 操作中产生的 undo log。因为 insert 操作的记录,只对事物本身可见,对其它事务不可见(事务隔离性的要求),故该 undo log 可在事务提交后直接删除,不需要进行 purge 操作。
- update undo log 记录的是对 delete 和 update 操作产生的 undo log。该 undo log 可能需要提供 MVCC 机制,因此不能在事务提交时就进行删除;提交时放入 undo log 链表,等待 purge 线程进行最后的删除。
- purge 线程
- 主要的作用是
清理undo页
和清理page里面带有Delete_Bit标识的数据行
。 - 在 InnoDB 中,事务中的 Delete 操作实际上并不是真正的删除掉数据行,而是一种Delete Mark操作,在记录上标识Delete_Bit,而不删除记录;
- 判断什么时间可以将 undo 页进行清除,请参考系列文章中 MVCC 的讲解。