重庆建网站哪家售后服务比较好,广告设计与制作前景,常见网页设计,新闻app开发公司背景 本文涉及的内容较为底层#xff0c;做了解即可#xff0c;是以前学习《高性能Mysql》和《mysql是怎样运行的》的笔记整理所得。 redolog(后续使用redo日志表示)的核心作用是保证数据库的持久性。 在mysql系列5—Innodb的缓存中介绍过#xff1a;数据和索引保存在磁盘上…背景 本文涉及的内容较为底层做了解即可是以前学习《高性能Mysql》和《mysql是怎样运行的》的笔记整理所得。 redolog(后续使用redo日志表示)的核心作用是保证数据库的持久性。 在mysql系列5—Innodb的缓存中介绍过数据和索引保存在磁盘上为提高效率读写时需从磁盘将数据加载到内存(Buffer Pool)中并基于内存进行读写。内存中的数据不稳定当系统断电或者崩溃时数据会丢失因此所有修改最终都要刷入磁盘。 数据库事务具有持久性事务提交成功后无论数据库环境如何(数据库或者操作系统崩溃)已提交事务涉及的修改都会被保存下来。 最简单的实现方法是每次事务提交都将Buffer Pool中的脏页刷入磁盘然后返回事务提交成功。这意味着每次事务提交时都进行刷盘会带来如下三个问题 [1] 刷盘相比内存操作的速度慢太多会严重影响mysql整体性能 [2] SQL可能只修改了一个字节然而需要以页为单位进行刷盘; [3] SQL可能影响多行记录这些行可以位于不同的页中涉及随机IO效率较低。 mysql引入redo日志为其提供了一个解决方案。redo日志通过特殊的格式设计从而占据较小的空间且redo日志的顺序写入相对随机IO效率较高。因此相对于直接将更新刷入磁盘将修改记录(redo日志)刷入磁盘可以在保证数据库持久性的前提下最大可能降低对数据库性能的影响。
1.redo文件介绍
本章先从整体上对redo文件的格式进行认识为后续章节作准备。内容包括对redo日志文件组、文件格式、块和每条redo日志格式的介绍泛泛了解即可。 整体结构图如上所示以下分别进行介绍。
1.1 redo文件组
redo文件以文件组的形式存在文件组首尾串联。先写向第一个文件写满后再转向下一个循环复用如下图所示: redo日志文件组的实现在mysql8和mysql5中有所区别。
mysql5
(1) 存放路径 由innodb_log_group_home_dir变量确定日志的存储路径默认与mysql的数据目录同一路径
mysql SHOW VARIABLES LIKE innodb_log_group_home_dir;
----------------------------------
| Variable_name | Value |
----------------------------------
| innodb_log_group_home_dir | ./ |
----------------------------------(2) 文件个数 由innodb_log_files_in_group变量确定文件组中文件的个数默认为2
mysql SHOW VARIABLES LIKE innodb_log_files_in_group;
----------------------------------
| Variable_name | Value |
----------------------------------
| innodb_log_files_in_group | 2 |
----------------------------------(3) 文件大小 由innodb_log_file_size变量确定每个文件的大小默认为50331648比特即48M.
mysql SHOW VARIABLES LIKE innodb_log_file_size;
--------------------------------
| Variable_name | Value |
--------------------------------
| innodb_log_file_size | 50331648 |
--------------------------------进入对应目录查询实际文件:
[root124 mysql]# cd /var/lib/mysql
[root124 mysql]# ls -al | grep ib_logfile
-rw-r-----. 1 mysql mysql 50331648 12月 15 09:46 ib_logfile0
-rw-r-----. 1 mysql mysql 50331648 10月 29 00:00 ib_logfile1mysql8
(1) 存放路径: 存放路径位于datadir的#innodb_redo文件夹相下即默认为/var/lib/mysql/#innodb_redo.
(2) 文件个数和文件大小 由变量innodb_redo_log_capacity控制文件组的整体大小默认为100M文件个数固定为32个因此每个文件大小为3276800比特.
[rootlocalhost #innodb_redo]# cd /var/lib/mysql/#innodb_redo
[rootlocalhost #innodb_redo]# ll -t
总用量 102400
-rw-r-----. 1 mysql mysql 3276800 12月 15 10:11 #ib_redo13030
-rw-r-----. 1 mysql mysql 3276800 12月 12 16:29 #ib_redo13029
-rw-r-----. 1 mysql mysql 3276800 12月 11 07:31 #ib_redo13028
-rw-r-----. 1 mysql mysql 3276800 12月 5 17:19 #ib_redo13059_tmp
-rw-r-----. 1 mysql mysql 3276800 12月 5 17:19 #ib_redo13058_tmp
-rw-r-----. 1 mysql mysql 3276800 12月 5 17:19 #ib_redo13057_tmp
...
-rw-r-----. 1 mysql mysql 3276800 12月 5 17:19 #ib_redo13033_tmp
-rw-r-----. 1 mysql mysql 3276800 12月 5 17:19 #ib_redo13032_tmp
-rw-r-----. 1 mysql mysql 3276800 12月 5 17:19 #ib_redo13031_tmp1.2 redo文件和redo块
对文件组有概念后再看一下redo文件。 每个redo文件由两部分组成: 文件头数据部分; 文件头占据2048个字节存储当前redo文件的管理信息数据部分存储redo日志。 redo文件也以页为单位进行管理不过一个redo页占据512K, 后面用log block表示(与前面介绍的数据页进行区分)。其中文件头占据前4个log block, 数据部分由多个log block(redo页)组成数据部分用于存放实际的redo数据。 再看一下log block的结构由头部、主体和尾部组成, 如下图所示: header和tailer存储log block的管理信息和校验信息body中存放redo数据。 其中header字段中有两个属性比较重要 [1] log_block_hdr_no: 表示log block的唯一ID; [2] log_block_hdr_data_len: 表示当前log block已使用了多少字节初始值为12(header长度)当496个body全部写完后log_block_hdr_data_len为512.[后面用到] 还有个地方需要注意一下(可先跳过看第5章时再折回) 尽管redo日志文件组的每个文件都有一个文件头且占据了2048个字节的管理信息仅第一个文件管理信息中使用两个log block(checkpoint1和chekpoint2)记录了checkpoint信息包括checkpoint_no(checkpoint编号)和checkpoint_lsn(checkpoint时的lsn); 每次checkpoint时checkpoint_no会递增根据奇偶性依次写入checkpoint1和checkpoint2以防止某次写错导致数据无法恢复。[后面用到]
1.3 redo日志格式
每条redo日志包含的信息有在哪个表空间的哪个页上做什么如: “将第10号表空间的100号页面的偏移量为1000处的值更新为10000”。 redo日志的格式设计如下: [1] type表示redo日志的类型决定了data的数据组成和解析过程type字段主要是为了节省空间; [2] spaceId和pageId表示表空间ID和数据页ID用于定位数据变更发生的的位置; [3] data记录改动的具体内容。
2.mini-transaction单元
上述redo日志格式中包含了表空间、页序号、修改数据等信息记录了一个修改操作的所有信息。mini-transaction(以下用mtr表示)是将一组相关联的redo日志组合成一个组mysql将一个mtr设计为一个原子操作。mtr的原子性设计为redo日志的持久化功能提供了基础以下结合案例进行说明。 假设B树索引的页A、B、C位于同一个表空间(tablespace001)页B比较空闲(只有一条记录)当插入一行主键为50的记录时索引树的变化如下图所示: 此时涉及一个改动在tablespace001的页B中添加一条记录50对应的redo日志只有一条记录近似表示为:
在tablespace001表空间的页B中添加一条记录50;然而如果页b已存满记录50通过计算需要存储在页b中此时需要经历页裂过程: 将页B裂解为页B和页D涉及的步骤有: [1] 创建一个页D; [2] 将页B的51和90记录前移到页D; [3] 在页A中新增一个目录记录项指向页D; [4] 将记录50添加到页B中. 此时对应的redo日志有多条记录, 近似表示为:
在tablespace001表空间创建一个页D;
在tablespace001表空间的页A中添加一个目录项指向页D
向tablespace001表空间的页D中添加记录51;
向tablespace001表空间的页D中添加记录90;
删除tablespace001表空间的页B的51记录;
删除tablespace001表空间的页B的90记录;
修改tablespace001表空间中页B的后驱节点为页D
修改tablespace001表空间中页D的前驱节点为页B
修改tablespace001表空间中页C的前驱节点为页D
修改tablespace001表空间中页D的后驱节点为页C
在tablespace001表空间的页B中添加一条记录50;
...
# 此外此时页A页的空间也不足够添加新的目录项记录则对页A需要进行裂解页A如果还有上级... 插入一条记录对一个索引树的修改对应多条记录这些redo日志需要具备原子性以保证B树的正确性如不能仅仅完成了页的裂解而没有拷贝数据不能仅创建了叶子节点而未在父节点新增目录项记录等。 上述这些redo日志被标记为一个mtr一个mtr内的redo日志必须完整。mtr的设计为日志的崩溃后恢复提供了较好的设计基础将在第5章中介绍。 上述一条INSERT语句仅仅是针对一个索引树一般一个表有多个索引因此一条INSERT语句一般对应多个mtr, 如自增主键也是一个mtr. 所以可用下图表示SQL与mtr与redo日志的关系:
3.log buffer
与Buffer Pool类似mysql引入log buffer作为redo日志的内存缓存以加快IO速度、提高系统性能。mysql启动时在内存中划出一块区域用于缓存redo日志这部分区域叫做log buffer. log buffer也是以log block为单元进行管理, 即redo日志先写入到log buffer再刷新到磁盘。 log buffer内存大小由innodb_log_buffer_size变量确定默认为16777216比特即16M.
mysql SHOW VARIABLES LIKE innodb_log_buffer_size;
----------------------------------
| Variable_name | Value |
----------------------------------
| innodb_log_buffer_size | 16777216 |
----------------------------------对于log buffer的理解可以从两个角度进行数据写入到log buffer以及log buffer刷盘到redo日志文件; 将redo日志写到缓冲区是以mtr为单元进行而将缓存写到磁盘是以redo页(log block)为单位进行。
3.1 写入log buffer
mysql将redo日志写入log buffer是以mtr为单位进行以下结合案例介绍写入过程。 假设有两个事物A和B事务A和B分别包括两个mtr, 按照时间的先后的执行顺序依次为mtr-a-1, mtr-b-1, mtr-a-2,mtr-a-2: 将mtr写入log buffer的流程图如下所示: 说明每个mtr都有一个唯一标识lsn即每个mtr内的所有redo日志具有相同的lsn。
3.2 log buffer写入磁盘
考虑到机器断电异常会导致内存数据丢失而redo日志本身就是为了保证数据库事务的持久性而引入因此何时对log buffer进行刷盘很重要。涉及到什么时候将log buffer刷入到磁盘。 log buffer的刷盘时机有以下几种: [1] log buffer空间不足时(占据了总容量的一半)需要刷盘以预留出足够的内存空间缓存新的redo日志; [2] 事务提交时刷盘; [3] 后台线程每隔1秒触发一次刷盘 [4] 正常关闭服务器时为了保证redo日志持久化需要刷盘; [5] checkpoint时将在第四章中介绍; 其中当事务提交时立即刷盘log buffer刷盘完成后上报事务提交成功保证了redo日志的持久性。 根据业务对持久性的要求不同mysql提供了一个调优参数innodb_flush_log_at_trx_commit.
mysql SHOW VARIABLES LIKE innodb_flush_log_at_trx_commit;
---------------------------------------
| Variable_name | Value |
---------------------------------------
| innodb_flush_log_at_trx_commit | 1 |
---------------------------------------[1] innodb_flush_log_at_trx_commit默认为1表示事务提交时立即将事务涉及的log buffer刷盘确保了事务的持久性 [2] 0表示不主动刷盘由后台线程异步刷盘(1s一次)如果数据库宕机会导致部分数据丢失; [3] 2表示将输入拷贝到操作系统缓冲区由操作系统刷盘; 数据库宕机不影响数据的持久性但操作系统宕机会导致数据丢失。 本文后续以默认的刷盘机制(innodb_flush_log_at_trx_commit1)进行介绍。
3.3 log buffer清理
在理解log buffer清理问题前需要先理解两个变量: lsn和flushed_to_disk_lsn。 lsn是个全局变量表示日志序列号(Log Sequeue Number)用于记录生成的redo日志量从8704开始不断递增。由于redo日志是以mtr为单位写入log buffer的所以整个mtr内的redo日志具有相同的lsn不同的mtr具备不同的lsn, 且lsn越小对表示redo日志生成的越早。 如上图所示mtr1对应的lsn是8704, mtr2对应的日志是8804(9704100)下一个mtr的lsn对应8954(8804150)后续lsn取决于上一个mtr的长度。 flushed_to_disk_lsn表示已被刷新到磁盘的lsn即lsn小于flushed_to_disk_lsn的redo日志已经被刷入磁盘这部分log buffer内存可被清理: 图中最左侧的log buffer区域可被清理和重复利用。当flushed_to_disk_lsn与LSN相同时表示所有的redo日志已刷入磁盘。 说明flushed_to_disk_lsn表示的是log buffer日志被刷入到磁盘还有一个write_lsn表示记录刷到了操作系统缓存。
4.redo日志文件清理
前面已经介绍了redo日志写入log buffer以及log buffer刷入磁盘的过程。flushed_to_disk_lsn变量表示已经写到磁盘的redo日志量lsn超出flushed_to_disk_lsn的redo日志可以从log buffer中被清理出。 本章需要思考对于已刷入磁盘的redo日志何时可以被清理。redo日志文件整体大小是固定的且redo日志组会循环使用因此需要一个合理的清理机制并需要考虑两个问题 [1] 确认哪些redo日志可以被清理数据被清理后不能影响持久性功能 [2] 何时清理需要有足够的空间保障log buffer的redo日志记录可以被刷入redo日志文件。
flush链表和lsn 对于flush链表的介绍在[mysql系列6—Innodb的缓存]中已详细介绍。 修改数据库时页的改动信息会以redo日志的形式记录下然后将涉及的改动页对应的控制块添加到flush链表中控制块之间形成双向链表。 flush链的控制块中保存了两个修改信息oldest_modification和newest_modification分别表示第一次修改时的lsn和最近一次修改结束后的lsn. 使用第三章中的案例从mysql启动后分别执行了两个mtr, mtr1占据100字节mtr2占据150字节: 假设mtr1修改了两个页面两个页面对应的控制块分别为控制块1和控制块2mtr2修改了一个页面页面控制块为控制块3, 则flush链表可以表示为: 图中O_M表示oldest_modification, N_M表示newest_modification. 页面被修改时页面对应的控制块会加入flush链的队列头部oldest_modification记录下当时的lsn, newest_modification会记录所在mtr结束时的lsn; 当后续这个页面再次被修改时仅修改newest_modification的值oldest_modification和位置不会发生变化即控制块是按照第一次修改的时间按从大到小的顺序排列的。 因此位于队列尾部的控制块的oldest_modification是整个flush链中最小的lsn(后续用最小LSN表示). 另外当脏页(修改过的数据页)被写入到磁盘后数据页对应的控制块会从flush链中删除因此redo日志文件中lsn小于**最小LSN可以被直接删除。 由此可以得出一个结论判断redo日志文件中日志是否可被删除的条件是脏页是否已经刷到磁盘了即redo日志的lsn是否小于最小LSN**。 由于全局LSN根据写入log buffer的mtr大小进行递增且log buffer与redo日志文件以log block为单位进行存储因此可根据lsn计算出对应redo日志在文件组中的偏移量。
checkpoint
上一节提到了一个**“最小LSN的概念mysql使用checkpiont_lsn表示。刷新一次checkpiont_lsn的操作被称为一次chekpoint操作会计算出当前系统的最小LSN并赋值给checkpiont_lsn: [1] 取出redo文件组的第一个文件的管理信息, 得到较大的checkpoint_no和对应的checkpoint_lsn; [2] 对checkpoint_no加1并写入文件; [3] 将当前最小LSN”**赋值给checkpoint_lsn并写入到文件中。 经历checkpoint后redo日志文件中可被清理的部分会被标记出清理操作由后台线程完成。
5.崩溃恢复
从崩溃中恢复是redo日志保证持久性的关键所在。数据库崩溃后根据redo日志进行数据库恢复可分为三个步骤确定恢复起点、确定恢复终点、执行恢复流程。
5.1 确认恢复起始点
章节1.2中介绍过redo日志文件组中第一个文件的管理信息中在两个block都存储了checkpoint_no和checkpoint_lsn的信息。其中较大的checkpoint_lsn表示上一次checkpoint操作对应的lsn即最小LSN. 由于lsn小于checkpoint_lsn的脏页已经被刷盘可通过checkpoint_lsn可以计算出在redo日志组中的偏移量使用该偏移量作为数据恢复的起点。 上图中控制块3表示未刷盘的最早被修改的页的控制块其oldest_modification是最小的lsn, 用作数据恢复的起点。
5.2 确认恢复终点
章节1.2中介绍过每个log block的header存储了log_block_hdr_data_len属性记录当前页使用的字节数。如果log_block_hdr_data_len等于512说明当前页已满小于512说明当前页未满未满的block表示最新的数据。通过未满的block的log_block_hdr_no可计算出在redo日志组中的偏移量作为数据恢复的终点。 上图中redo日志文件的第100个log block已满而第101个未满101最后一个redo日志的lsn作为日志恢复终点的lsn.
5.3 恢复流程
恢复的时候需要注意一个问题checkpoint_lsn之前的redo日志对应的脏页已经刷入磁盘了但checkpoint_lsn之后的可能已刷盘也可能没有需要区分对待。 每个数据页都有一个File Header文件头通过file_page_lsn属性记录了最近一次修改页面时的lsn值(对应flush链中的newest_modification)。在checkpoint之后刷入磁盘的脏页file_page_lsn大于checkpoint_lsn对于这部分数据页恢复时直接跳过file_page_lsn和数据页的内容可参考mysql系列5—Innodb的缓存。 通过redo日志组的恢复起点偏移量和终点偏移量可以得到一段redo日志再根据file_page_lsn过滤掉一部分得到一个redo日志数组。 这些redo日志数组可能涉及不同表空间的不同数据页的修改操作如redo-1日志修改表空间1-数据页1redo-2修改表空间100-数据页100redo-3修改表空间1-数据页1redo-4修改表空间100-数据页100,… 随机写的概率比较大可根据(表空间ID, 数据页页号)通过哈希算法对redo日志进行映射分组然后按照组依次执行将随机IO转为多个连续IO从而提高系统效率。