searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

MySQL物理日志REDO的LSN管理与文件管理

2023-06-08 10:18:08
10
0
LSN管理和文件管理分层,在redo log模块来看只有逻辑无限的current_file_lsn和逻辑上连续但空间有限的current_file_real_offset,定位时只需要根据二者计算便可算出对应的逻辑offset,然后交给文件管理模块去定位到具体文件具体偏移读取,这样简化了redo log模块维护这些文件偏移的代价
 
LSN 与 文件的对应关系主要与以下几个变量有关,这几个变量如下,在log_sys_init()对值以下值进行了初始化:
current_file_lsn: Some lsn value within the current log file. 初始化为: LOG_START_LSN;
current_file_real_offset:File offset for the current_file_lsn.初始化为:LOG_FILE_HDR_SIZE;
current_file_end_offset: Up to this file offset we are within the same current log file.
files_real_capacity: Total capacity of all the log files (file_size * n_files), including headers of the log files.
n_files: 最少为2个,最多为100个
file_size:如果没有被用户指定,那么file_size的大小就是根据buff pool的大小来计算的,具体可参看函数:innodb_log_file_size_init()
 
下面进行详细讲解:
图中有两个redo log文件,ib_logfile0和ib_logfile1,每个文件4K(包含2K的FileHeader和4个512B的Block),下面就来看下InnoDB是如何使用这2个文件来管理redo log的:
第一层连续蓝色块是要写的redo log原始内容,sn作为原始内容的序列号,初始值是7936,随着写入增加,每次增加一个mtr中原始log的长度。不过实际写入文件中的并不只是原始内容,InnoDB会将原始内容按496B为单位切分,加上16B的BlockHeader和BlockTrailer,凑成一个512B的Block,这样实际写入log文件的内容是类似图中第二层的格式,一个Block接一个Block。由于sn只表示原始内容的序列号,现在加入了Header和Trailer,那么对应的变化之后的序列号就由current_file_lsn来表示,初始值为8192,sn和current_file_lsn相互转换方式如下:
// sn 转换到 lsn // 给sn每496B都增加16B的头尾,结果即为对应的lsn constexpr inline lsn_t log_translate_sn_to_lsn(lsn_t sn) { return (sn / LOG_BLOCK_DATA_SIZE * OS_FILE_LOG_BLOCK_SIZE + sn % LOG_BLOCK_DATA_SIZE + LOG_BLOCK_HDR_SIZE); } // lsn 转换到 sn // 给lsn每512B都减去16B的头尾,再加上最后一个不完整的Block内偏移,减去 // 该Block的Header 12B,结果即为对应的sn inline lsn_t log_translate_lsn_to_sn(lsn_t lsn) { /* Calculate sn of the beginning of log block, which contains the provided lsn value. */ const sn_t sn = lsn / OS_FILE_LOG_BLOCK_SIZE * LOG_BLOCK_DATA_SIZE; /* Calculate offset for the provided lsn within the log block. The offset includes LOG_BLOCK_HDR_SIZE bytes of block's header. */ const uint32_t diff = lsn % OS_FILE_LOG_BLOCK_SIZE; if (diff < LOG_BLOCK_HDR_SIZE) { /* The lsn points to some bytes inside the block's header. Return sn for the beginning of the block. Note, that sn values don't enumerate bytes of blocks' headers, so the value of diff does not matter at all. */ return (sn); } if (diff > OS_FILE_LOG_BLOCK_SIZE - LOG_BLOCK_TRL_SIZE) { /* The lsn points to some bytes inside the block's footer. Return sn for the beginning of the next block. Note, that sn values don't enumerate bytes of blocks' footer, so the value of diff does not matter at all. */ return (sn + LOG_BLOCK_DATA_SIZE); } /* Add the offset but skip bytes of block's header. */ return (sn + diff - LOG_BLOCK_HDR_SIZE); }
current_file_lsn同样是不断增加并且不回环的,它是redo log实际内容在逻辑上的增长,所以理论上是支持到无限大(UINT64_MAX),代表可以写入这么多redo log,但实际上的情况并非这样,redo log文件个数存在多个(图中有2个),这样无限增长的current_file_lsn需要知道什么时候换需要换下一个文件开始写,什么时候所有文件都已经写完需要回绕到第一个文件开始写。这里就需要图中第三层的转换,由于图中只有2个文件,每个文件4K,那么实际能写入的内容上限即是8K,刨除两个文件的FileHeader 4K,实际有效内容只剩4K,也就是说current_file_lsn每增长2K就要切换到下一个文件,每增长4K就要回绕到第一个文件。这里有一点需要注意,redo log这里其实对单个文件的感知并不强,在切换文件时它并不会显式的切换fd,具体切换是在fil_system里根据page_id做的,redo log模块这里认为下面的两个文件组成的实际上是一个逻辑上连续的8K空间,current_file_real_offset代表这8K内的偏移,初始值为2048(前面是第一个文件的FileHeader),初始时current_file_real_offset和current_file_lsn对应起来,即(2048 <---> 8192),之后的每次写入都同步更新这两个值,就可以完成逻辑上无限的current_file_lsn到实际有限的current_file_real_offset的映射转换。另外,current_file_end_offset代表当前在写的这个文件对应的结尾位置,如果current_file_real_offset超过这个位置就需要将其加上2K Header表示切换到下一个文件,files_real_capacity表示2个文件实际大小总和,这里即8K,如果current_file_real_offset超过这个值,代表当前2个文件都已经写完了,需要回绕到第一个文件重新写,这里就会将current_file_real_offset重新置为2048,完成回绕。
 
给定LSN,在日志文件中找到它存储的位置(文件以及在文件内的偏移),可以参考函数log_files_real_offset_for_lsn()的实现。其核心代码实现如下:
uint64_t log_files_real_offset_for_lsn(const log_t &log, lsn_t lsn) { uint64_t size_offset; uint64_t size_capacity; uint64_t delta; ut_ad(log_writer_mutex_own(log)); size_capacity = log.n_files * (log.file_size - LOG_FILE_HDR_SIZE); if (lsn >= log.current_file_lsn) { delta = lsn - log.current_file_lsn; delta = delta % size_capacity; } else { /* Special case because lsn and offset are unsigned. */ delta = log.current_file_lsn - lsn; delta = size_capacity - delta % size_capacity; } size_offset = log_files_size_offset(log, log.current_file_real_offset); size_offset = (size_offset + delta) % size_capacity; return (log_files_real_offset(log, size_offset)); }
 
0条评论
0 / 1000
yyy
6文章数
0粉丝数
yyy
6 文章 | 0 粉丝
原创

MySQL物理日志REDO的LSN管理与文件管理

2023-06-08 10:18:08
10
0
LSN管理和文件管理分层,在redo log模块来看只有逻辑无限的current_file_lsn和逻辑上连续但空间有限的current_file_real_offset,定位时只需要根据二者计算便可算出对应的逻辑offset,然后交给文件管理模块去定位到具体文件具体偏移读取,这样简化了redo log模块维护这些文件偏移的代价
 
LSN 与 文件的对应关系主要与以下几个变量有关,这几个变量如下,在log_sys_init()对值以下值进行了初始化:
current_file_lsn: Some lsn value within the current log file. 初始化为: LOG_START_LSN;
current_file_real_offset:File offset for the current_file_lsn.初始化为:LOG_FILE_HDR_SIZE;
current_file_end_offset: Up to this file offset we are within the same current log file.
files_real_capacity: Total capacity of all the log files (file_size * n_files), including headers of the log files.
n_files: 最少为2个,最多为100个
file_size:如果没有被用户指定,那么file_size的大小就是根据buff pool的大小来计算的,具体可参看函数:innodb_log_file_size_init()
 
下面进行详细讲解:
图中有两个redo log文件,ib_logfile0和ib_logfile1,每个文件4K(包含2K的FileHeader和4个512B的Block),下面就来看下InnoDB是如何使用这2个文件来管理redo log的:
第一层连续蓝色块是要写的redo log原始内容,sn作为原始内容的序列号,初始值是7936,随着写入增加,每次增加一个mtr中原始log的长度。不过实际写入文件中的并不只是原始内容,InnoDB会将原始内容按496B为单位切分,加上16B的BlockHeader和BlockTrailer,凑成一个512B的Block,这样实际写入log文件的内容是类似图中第二层的格式,一个Block接一个Block。由于sn只表示原始内容的序列号,现在加入了Header和Trailer,那么对应的变化之后的序列号就由current_file_lsn来表示,初始值为8192,sn和current_file_lsn相互转换方式如下:
// sn 转换到 lsn // 给sn每496B都增加16B的头尾,结果即为对应的lsn constexpr inline lsn_t log_translate_sn_to_lsn(lsn_t sn) { return (sn / LOG_BLOCK_DATA_SIZE * OS_FILE_LOG_BLOCK_SIZE + sn % LOG_BLOCK_DATA_SIZE + LOG_BLOCK_HDR_SIZE); } // lsn 转换到 sn // 给lsn每512B都减去16B的头尾,再加上最后一个不完整的Block内偏移,减去 // 该Block的Header 12B,结果即为对应的sn inline lsn_t log_translate_lsn_to_sn(lsn_t lsn) { /* Calculate sn of the beginning of log block, which contains the provided lsn value. */ const sn_t sn = lsn / OS_FILE_LOG_BLOCK_SIZE * LOG_BLOCK_DATA_SIZE; /* Calculate offset for the provided lsn within the log block. The offset includes LOG_BLOCK_HDR_SIZE bytes of block's header. */ const uint32_t diff = lsn % OS_FILE_LOG_BLOCK_SIZE; if (diff < LOG_BLOCK_HDR_SIZE) { /* The lsn points to some bytes inside the block's header. Return sn for the beginning of the block. Note, that sn values don't enumerate bytes of blocks' headers, so the value of diff does not matter at all. */ return (sn); } if (diff > OS_FILE_LOG_BLOCK_SIZE - LOG_BLOCK_TRL_SIZE) { /* The lsn points to some bytes inside the block's footer. Return sn for the beginning of the next block. Note, that sn values don't enumerate bytes of blocks' footer, so the value of diff does not matter at all. */ return (sn + LOG_BLOCK_DATA_SIZE); } /* Add the offset but skip bytes of block's header. */ return (sn + diff - LOG_BLOCK_HDR_SIZE); }
current_file_lsn同样是不断增加并且不回环的,它是redo log实际内容在逻辑上的增长,所以理论上是支持到无限大(UINT64_MAX),代表可以写入这么多redo log,但实际上的情况并非这样,redo log文件个数存在多个(图中有2个),这样无限增长的current_file_lsn需要知道什么时候换需要换下一个文件开始写,什么时候所有文件都已经写完需要回绕到第一个文件开始写。这里就需要图中第三层的转换,由于图中只有2个文件,每个文件4K,那么实际能写入的内容上限即是8K,刨除两个文件的FileHeader 4K,实际有效内容只剩4K,也就是说current_file_lsn每增长2K就要切换到下一个文件,每增长4K就要回绕到第一个文件。这里有一点需要注意,redo log这里其实对单个文件的感知并不强,在切换文件时它并不会显式的切换fd,具体切换是在fil_system里根据page_id做的,redo log模块这里认为下面的两个文件组成的实际上是一个逻辑上连续的8K空间,current_file_real_offset代表这8K内的偏移,初始值为2048(前面是第一个文件的FileHeader),初始时current_file_real_offset和current_file_lsn对应起来,即(2048 <---> 8192),之后的每次写入都同步更新这两个值,就可以完成逻辑上无限的current_file_lsn到实际有限的current_file_real_offset的映射转换。另外,current_file_end_offset代表当前在写的这个文件对应的结尾位置,如果current_file_real_offset超过这个位置就需要将其加上2K Header表示切换到下一个文件,files_real_capacity表示2个文件实际大小总和,这里即8K,如果current_file_real_offset超过这个值,代表当前2个文件都已经写完了,需要回绕到第一个文件重新写,这里就会将current_file_real_offset重新置为2048,完成回绕。
 
给定LSN,在日志文件中找到它存储的位置(文件以及在文件内的偏移),可以参考函数log_files_real_offset_for_lsn()的实现。其核心代码实现如下:
uint64_t log_files_real_offset_for_lsn(const log_t &log, lsn_t lsn) { uint64_t size_offset; uint64_t size_capacity; uint64_t delta; ut_ad(log_writer_mutex_own(log)); size_capacity = log.n_files * (log.file_size - LOG_FILE_HDR_SIZE); if (lsn >= log.current_file_lsn) { delta = lsn - log.current_file_lsn; delta = delta % size_capacity; } else { /* Special case because lsn and offset are unsigned. */ delta = log.current_file_lsn - lsn; delta = size_capacity - delta % size_capacity; } size_offset = log_files_size_offset(log, log.current_file_real_offset); size_offset = (size_offset + delta) % size_capacity; return (log_files_real_offset(log, size_offset)); }
 
文章来自个人专栏
MySQL
6 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0