事务的隔离性由 锁机制
实现
事务的原子性、一致性、隔离性 由事务的 redo日志 和 undo 日志来保证
- redo log 称为
重做日志
,提供再写入操作,恢复提交事务修改的页操作,用来保证事务的持久性。 - undo log 称为
回滚日志
,回滚行记录到某个特定的版本,用来保证事务的原子性、一致性。
1、redo日志
- InnoDB存储引擎是
以页为单位
来管理存储空间的。在正真访问页之前,需要把磁盘上
的页缓存到内存中的buffer pool
后才能访问。所有的变更都必须先更新缓冲池
中的数据,然后缓冲池中的脏页
会以一定频率被刷新到磁盘(checkpoint机制
)。
1.1、redo日志的好处
- 降低了刷新磁盘的频率
- 占用的空间非常小
存储表空间ID、页号、偏移量以及需要更新的值,所需的存储空间是很小的,刷新磁盘块
1.2、redo日志的特点
- 是顺序写入磁盘的
在事务执行过程中,每执行一条语句就可能产生若干条redo日志,这些日志是按照 产生的顺序写入磁盘的,使用顺序IO效率比随机IO快
- 事务执行过程中,redo log 不断记录
redo log 和 bin log 的区别在于 redo log 是存储引擎产生的,而bin log 是数据库层产生的。
比如一个事务对表做大量行的记录插入操作,在这个过程中,一致不断往redo log 顺序记录,而bin log 不会记录,直到事务提交,才会写入到bin log文件中
1.3、redo的组成
重做日志的缓冲
(redo log buffer),保存在内存中,是易丢失的。 参数设置 :innodb_log_buffer_size
默认16M
,最大值是4096M 最小值为1M
mysql> show variables like 'innodb_log_buffer_size';
+------------------------+----------+
| Variable_name | Value |
+------------------------+----------+
| innodb_log_buffer_size | 16777216 |
+------------------------+----------+
1 row in set (3.29 sec)
在服务器启动时就向操作系统申请一大片 redo log buffer 的连续内存空间,即redo日志缓冲区。这片内存空间被划分为若干个连续的 redo log block。一个 redo log block 占用 512字节大小。
重做日志文件
(redo log file),保存在硬盘中,是持久的
1.4、redo的执行整体流程
- 以更新事务举例,如下所示
①:先将原始数据从磁盘读入内存,修改数据的内存拷贝
②:生成一条重做日志并写入redo log buffer,记录数据被修改后的值
③:当事务commit时,将redo log buffer 中的内容刷新到 redo log file,对redo log file 采用追加写的方式
④:定期将内存中修改的数据刷新到磁盘中
write-ahead log(预先日志持久化):在持久化一个数据页之前先将内存中相应的日志页持久化
1.5、redo log 刷盘策略
- 参数设置:
innodb_flush_log_at_trx_commit
,支持以下三种策略:- 设置为0:表示每次事务提交时不进行刷盘操作。(系统默认master thread 每隔1s进行一次重做日志的同步)
- 设置为1:表示每次事务提交时都将进行同步,刷盘操作(默认值)
- 设置为2:表示每次事务提交时都只把redo log buffer 内容写入page cache,不进行同步。
mysql> show variables like 'innodb_flush_log_at_trx_commit';
+--------------------------------+-------+
| Variable_name | Value |
+--------------------------------+-------+
| innodb_flush_log_at_trx_commit | 1 |
+--------------------------------+-------+
1 row in set (1.64 sec)
1.6、写入 redo log buffer 过程
1.6.1、Mini-Transaction
MySQL对底层页面一次原子性访问的过程称为一个 Mini-Transaction,简称 mtr。
例如:向某个索引对应的B+树中插入一条记录的过程就是一个Mini-Transaction。一个所谓的mtr 可以包含一组redo日志,在进行崩溃恢复时这一组redo日志作为一个不可分割的整体。
- 一个事务可以包含若干条语句,每一条语句其实由若干个mtr组成,每个mtr又可以包含若干条redo日志。如下图所示
1.6.2、日志写入log buffer
向log buffer 中写入redo日志的过程是顺序的,也就是先往前边的block中写,当该block的空闲空间用完之后再往下一个block中写。当往log buffer中写入redo 日志时,设计者提供一了一个 buf_free的全局变量,该变量指明后续写入的redo日志应该写入到log_buffer中的哪个位置。如下图所示
- 一个mtr执行过程中可能产生若干条redo日志,这些redo日志是一个不可分割的组。并不是没生成一条redo日志就将其插入到log buffer 中,而是每个mtr运行过程中产生的日志先暂时存放到一个位置,当该mtr结束的时候将过程中产生的一组redo日志全部复制到 log buffer中。
1.6.3、redo log block的结构图
- 一个redo log block是由 日志头(12字节)、日志体(492字节)、日志尾(8字节)组成。
1.7、redo log file
1.7.1、相关参数设置
innodb_log_group_home_dir
:指定redo log 文件组所在的路径,默认值为./
,表示再数据库的数据目录下。MySQL的默认数据目录(var/lib/mysql)下默认有两个名为ib_logfile0 和 ib_logfile1的文件,log buffer 中的日志默认情况下就是刷新到这两个磁盘文件中。此redo日志文件位置还可以修改。
mysql> show variables like 'innodb_log_group_home_dir';
+---------------------------+-------+
| Variable_name | Value |
+---------------------------+-------+
| innodb_log_group_home_dir | ./ |
+---------------------------+-------+
1 row in set (3.70 sec)
innodb_log_files_in_group
:指明redo log file 的个数,默认 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.00 sec)
- innodb_flush_log_at_tx_commit : 控制redo log 刷新到磁盘的策略,默认为 1
mysql> show variables like 'innodb_flush_log_at_trx_commit';
+--------------------------------+-------+
| Variable_name | Value |
+--------------------------------+-------+
| innodb_flush_log_at_trx_commit | 1 |
+--------------------------------+-------+
1 row in set (1.64 sec)
- innodb_log_file_size:单个redo log 文件设置大小,默认值为48M。最大值为512G(指整个redo log 系列文件之和)。
mysql> show variables like 'innodb_log_file_size';
+----------------------+----------+
| Variable_name | Value |
+----------------------+----------+
| innodb_log_file_size | 50331648 |
+----------------------+----------+
1 row in set (0.00 sec)
1.7.2、日志文件组
- redo日志文件以日志文件组的形式出现。文件以ib_logfile[正整数]的形式命名,每个redo日志文件都是一样大小。
- redo日志写入顺序依次执行,当最后一个文件写满后会从第一个接续写。
1.7.3、checkpoint
在整个日志文件组中还有两个重要的属性,分别时 write pos 、checkpoint
- write pos 是记录当前位置,一边写一边后移
- checkpoint 是当前要擦除的位置,也是往后推移
- 每次刷盘 redo log 记录到日志文件组中,write pos 位置就会后移更新。每次MySQL加载日志文件组恢复数据时,会清空加载过的 redo log 记录,并把checkpoint后移更新。write pos 和checkpoint 之间还空闲的部分可以写入新的redo log 记录。
2、undo日志
- undo log是事务的原子性保证。在事务中更新数据 的前置操作其实就是要先写入一个 undo log
2.1、undo log 的理解
- 事务需要保证原子性,也就是事务的操作要么全部完成,要么都不做,但有时候事务执行到一半会出现以下情况:
- 事务执行过程中会遇到各种错误,如:服务器本身错误,操作系统错误等
- 在事务执行过程中手动rollback操作结束当前事务的执行
2.2、undo log 的作用
- 回滚数据
- 并发版本控制(MVCC)
2.3、undo 的存储结构
2.3.1、回滚段与undo页
-
innodb对undo log管理采用段的方式 即回滚段(rollback segment)。每个回滚段记录了 1024个undo log segment,而每个undo log segment段中进行undo页的申请。
- 在innodb1.1版本之前,只有一个rollback segment,因此支持同时在线的事务限制为1024.
- innodb1.1开始支持最大128个 rollback segment,故其支持同时在线的事务限制提高到了 128*1024。
-
通过查询参数 innodb_rollback_segments
mysql> show variables like 'innodb_rollback_segments';
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| innodb_rollback_segments | 128 |
+--------------------------+-------+
1 row in set (0.01 sec)
- innodb_undo_directory:设置rollback segment 文件所在路径。
mysql> show variables like 'innodb_undo_directory';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| innodb_undo_directory | ./ |
+-----------------------+-------+
1 row in set (0.13 sec)
- innodb_undo_tablespaces: 设置构成rollback segment文件的数量,这样rollback segment 可以较为平均的分布在多个文件中。设置该参数后会在路径innodb_undo_directory看到undo为前缀的文件,该文件就表示rollback segment文件。
mysql> show variables like 'innodb_undo_tablespaces';
+-------------------------+-------+
| Variable_name | Value |
+-------------------------+-------+
| innodb_undo_tablespaces | 2 |
+-------------------------+-------+
1 row in set (0.12 sec)
undo页的重用
- 当我们开启一个事务需要写入undo log的时候,就得先去undo log segment中找到一个空闲的空间,当有空位的时候就去申请undo页,在这个申请到的undo页中进行undo log 的写入。
- undo log 在commit后会被放到一个链表中,然后判断undo页的使用空间是否小于3/4。如果小于则表示当前的undo页可以被重用,不会被回收掉,其他事务的undo log 可以记录在当前undo页的后面。
- 由于undo log 是 离散的 ,在清理对应磁盘空间时,效率不高。
2.3.2、回滚段与事务
- 每个事务只会使用一个回滚段,一个回滚段在同一时刻可能会服务于多个事务
- 当一个事务开始的时候,会制定一个回滚段,在事务进行过程中,当数据被修改时,原始的数据会被复制到回滚段。
- 在回滚段中,事务会不断填充盘区,直到事务结束或所有的空间被用完。如果当前的盘区不够用,事务会在段中请求下一个盘区,如果占用盘区被用完,事务会覆盖最初盘区或者在回滚段允许的情况下拓展新的盘区来使用。
- 回滚段存在于 undo表空间中,在数据库中可以存在多个表空间,但同一时刻只能使用一个undo表空间。
- 当事务提交时,innodb存储引擎会做以下两件事情:
- 将undo log 放入列表中以供之后的purge操作
- 判断undo log 所在的页是否可以重用,若可以分配给下一个事务使用
2.3.3、回滚段中数据分类
- 未提交的回滚数据:该数据所关联的事务并未提交,用于实现读一致性,所以数据不能被其他事务覆盖
- 已经提交但未过期的回滚数据:该数据关联的事务已经提交,但是仍收到 undo retention参数的保持时间的影响。
- 已经提交并过期的回滚:事务已经提交,而且数据保存时间已经超过 undo retention参数指定的时间,属于已经过期的数据。当回滚段满后,会优先覆盖事务已经提交并过期的数据。
2.4、undo的类型
- insert undo log:指在inset操作中产生的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,而不删除记录,是一种“假删除”,知识做了相关标记,真正的删除工作需要后台的purge线程去完成。