IT评测·应用市场-qidao123.com

标题: 海山数据库(He3DB)源码详解:海山MySQL redo日志-日志文件组 [打印本页]

作者: 大号在练葵花宝典    时间: 2024-12-2 00:58
标题: 海山数据库(He3DB)源码详解:海山MySQL redo日志-日志文件组
# 1、日志文件组
MySQL的数据目次下默认有两个名为ib_logfile0和ib_logfile1的文件,log buffer中的日志默认环境下就是刷新到这两个磁盘文件中。
假如对默认的redo日志文件不满足,可以通过下边几个启动参数来调治:

从上边的描述中可以看到,磁盘上的redo日志文件不但一个,而是以一个日志文件组的形式出现的。这些文件以ib_logfile[数字](数字可以是0、1、2…)的形式进行命名。在将redo日志写入日志文件组时,是从ib_logfile0开始写,假如ib_logfile0写满了,就接着ib_logfile1写,同理,ib_logfile1写满了就去写ib_logfile2,依此类推。假如写到最后一个文件该咋办?那就重新转到ib_logfile0继承写,所以整个过程如下图所示:

统共的redo日志文件巨细实在就是:innodb_log_file_size × innodb_log_files_in_group
2、redo日志文件格式

前文说过log buffer本质上是一片连续的内存空间,被分别成了多少个512字节巨细的block。将log buffer中的redo日志刷新到磁盘的本质就是把block的镜像写入日志文件中,所以redo日志文件实在也是由多少个512字节巨细的block组成。redo日志文件组中的每个文件巨细都一样,格式也一样,都是由两部分组成:


这里需要介绍一下每个redo日志文件前2048个字节,也就是前4个特别block的格式是怎样的,如图所示。

其中,第一个block为log file header,包罗以下属性:


第二个block为checkpoint1,该块中记录了关于checkpoint一些属性:


第四个blockcheckpoint2与checkpoint1中的结构属性相同,因此不做赘述。
3、源码解析

3.1 日志文件组结构体

  1. struct log_group_t{
  2.         /** log group identifier (always 0) */
  3.         ulint                                id;
  4.         /** number of files in the group */
  5.         ulint                                n_files;
  6.         /** format of the redo log: e.g., LOG_HEADER_FORMAT_CURRENT */
  7.         ulint                                format;
  8.         /** individual log file size in bytes, including the header */
  9.         lsn_t                                file_size
  10.         /** file space which implements the log group */;
  11.         ulint                                space_id;
  12.         /** corruption status */
  13.         log_group_state_t                state;
  14.         /** lsn used to fix coordinates within the log group */
  15.         lsn_t                                lsn;
  16.         /** the byte offset of the above lsn */
  17.         lsn_t                                lsn_offset;
  18.         /** unaligned buffers */
  19.         byte**                                file_header_bufs_ptr;
  20.         /** buffers for each file header in the group */
  21.         byte**                                file_header_bufs;
  22.         /** used only in recovery: recovery scan succeeded up to this
  23.         lsn in this log group */
  24.         lsn_t                                scanned_lsn;
  25.         /** unaligned checkpoint header */
  26.         byte*                                checkpoint_buf_ptr;
  27.         /** buffer for writing a checkpoint header */
  28.         byte*                                checkpoint_buf;
  29.         /** list of log groups */
  30.         UT_LIST_NODE_T(log_group_t)        log_groups;
  31. };
复制代码
日志文件组结构体中包罗了文件组id、文件数目、所包罗redo日志文件的类型等基本信息。别的,结构体中还有几个较为重要的字段:

3.2 刷盘时机

抱负状态下,事务一提交就会进行刷盘操纵,但实际上,刷盘的时机是根据策略来进行的。
InnoDB存储引擎为redo log的刷盘策略提供了innodb_flush_log_at_trx_commit参数,它支持三种策略:

3.2.1 提交刷盘

提交刷盘主要涉及到两个关键函数:log_buffer_flush_to_disk()和log_write_up_to()
  1. void
  2. log_buffer_flush_to_disk(
  3.         bool sync)
  4. {
  5.         ut_ad(!srv_read_only_mode);
  6.         log_write_up_to(log_get_lsn(), sync);
  7. }
复制代码

  1. void
  2. log_write_up_to(
  3.         lsn_t        lsn,
  4.         bool        flush_to_disk)
  5. {
  6.         byte*           write_buf;
  7.         lsn_t           write_lsn;
  8.         ut_ad(!srv_read_only_mode);
  9.         if (recv_no_ibuf_operations) {
  10.                 return;
  11.         }
  12. loop:
  13.         ut_ad(++loop_count < 128);
  14.         log_write_mutex_enter();
  15.         ut_ad(!recv_no_log_write);
  16.         lsn_t        limit_lsn = flush_to_disk
  17.                 ? log_sys->flushed_to_disk_lsn
  18.                 : log_sys->write_lsn;
  19.         if (limit_lsn >= lsn) {
  20.                 log_write_mutex_exit();
  21.                 return;
  22.         }
  23. #ifdef _WIN32
  24. # ifndef UNIV_HOTBACKUP
  25.         /* write requests during fil_flush() might not be good for Windows */
  26.         if (log_sys->n_pending_flushes > 0
  27.             || !os_event_is_set(log_sys->flush_event)) {
  28.                 log_write_mutex_exit();
  29.                 os_event_wait(log_sys->flush_event);
  30.                 goto loop;
  31.         }
  32. # else
  33.         if (log_sys->n_pending_flushes > 0) {
  34.                 goto loop;
  35.         }
  36. # endif  /* !UNIV_HOTBACKUP */
  37. #endif /* _WIN32 */
  38.         if (flush_to_disk
  39.             && (log_sys->n_pending_flushes > 0
  40.                 || !os_event_is_set(log_sys->flush_event))) {
  41.                 /* Figure out if the current flush will do the job
  42.                 for us. */
  43.                 bool work_done = log_sys->current_flush_lsn >= lsn;
  44.                 log_write_mutex_exit();
  45.                 os_event_wait(log_sys->flush_event);
  46.                 if (work_done) {
  47.                         return;
  48.                 } else {
  49.                         goto loop;
  50.                 }
  51.         }
  52.         log_mutex_enter();
  53.         if (!flush_to_disk
  54.             && log_sys->buf_free == log_sys->buf_next_to_write) {
  55.                 /* Nothing to write and no flush to disk requested */
  56.                 log_mutex_exit_all();
  57.                 return;
  58.         }
  59.         log_group_t*        group;
  60.         ulint                start_offset;
  61.         ulint                end_offset;
  62.         ulint                area_start;
  63.         ulint                area_end;
  64.         ulong                write_ahead_size = srv_log_write_ahead_size;
  65.         ulint                pad_size;
  66.         DBUG_PRINT("ib_log", ("write " LSN_PF " to " LSN_PF,
  67.                               log_sys->write_lsn,
  68.                               log_sys->lsn));
  69.         if (flush_to_disk) {
  70.                 log_sys->n_pending_flushes++;
  71.                 log_sys->current_flush_lsn = log_sys->lsn;
  72.                 MONITOR_INC(MONITOR_PENDING_LOG_FLUSH);
  73.                 os_event_reset(log_sys->flush_event);
  74.                 if (log_sys->buf_free == log_sys->buf_next_to_write) {
  75.                         /* Nothing to write, flush only */
  76.                         log_mutex_exit_all();
  77.                         log_write_flush_to_disk_low();
  78.                         return;
  79.                 }
  80.         }
  81.         start_offset = log_sys->buf_next_to_write;
  82.         end_offset = log_sys->buf_free;
  83.         area_start = ut_calc_align_down(start_offset, OS_FILE_LOG_BLOCK_SIZE);
  84.         area_end = ut_calc_align(end_offset, OS_FILE_LOG_BLOCK_SIZE);
  85.         ut_ad(area_end - area_start > 0);
  86.         log_block_set_flush_bit(log_sys->buf + area_start, TRUE);
  87.         log_block_set_checkpoint_no(
  88.                 log_sys->buf + area_end - OS_FILE_LOG_BLOCK_SIZE,
  89.                 log_sys->next_checkpoint_no);
  90.         write_lsn = log_sys->lsn;
  91.         write_buf = log_sys->buf;
  92.         log_buffer_switch();
  93.         group = UT_LIST_GET_FIRST(log_sys->log_groups);
  94.         log_group_set_fields(group, log_sys->write_lsn);
  95.         log_mutex_exit();
  96.         /* Calculate pad_size if needed. */
  97.         pad_size = 0;
  98.         if (write_ahead_size > OS_FILE_LOG_BLOCK_SIZE) {
  99.                 lsn_t        end_offset;
  100.                 ulint        end_offset_in_unit;
  101.                 end_offset = log_group_calc_lsn_offset(
  102.                         ut_uint64_align_up(write_lsn,
  103.                                            OS_FILE_LOG_BLOCK_SIZE),
  104.                         group);
  105.                 end_offset_in_unit = (ulint) (end_offset % write_ahead_size);
  106.                 if (end_offset_in_unit > 0
  107.                     && (area_end - area_start) > end_offset_in_unit) {
  108.                         /* The first block in the unit was initialized
  109.                         after the last writing.
  110.                         Needs to be written padded data once. */
  111.                         pad_size = write_ahead_size - end_offset_in_unit;
  112.                         if (area_end + pad_size > log_sys->buf_size) {
  113.                                 pad_size = log_sys->buf_size - area_end;
  114.                         }
  115.                         ::memset(write_buf + area_end, 0, pad_size);
  116.                 }
  117.         }
  118.         /* Do the write to the log files */
  119.         log_group_write_buf(
  120.                 group, write_buf + area_start,
  121.                 area_end - area_start + pad_size,
  122.                 ut_uint64_align_down(log_sys->write_lsn,
  123.                                      OS_FILE_LOG_BLOCK_SIZE),
  124.                 start_offset - area_start);
  125.         srv_stats.log_padded.add(pad_size);
  126.         log_sys->write_lsn = write_lsn;
  127. #ifndef _WIN32
  128.         if (srv_unix_file_flush_method == SRV_UNIX_O_DSYNC) {
  129.                 /* O_SYNC means the OS did not buffer the log file at all:
  130.                 so we have also flushed to disk what we have written */
  131.                 log_sys->flushed_to_disk_lsn = log_sys->write_lsn;
  132.         }
  133. #endif /* !_WIN32 */
  134.         log_write_mutex_exit();
  135.         if (flush_to_disk) {
  136.                 log_write_flush_to_disk_low();
  137.         }
  138. }
复制代码

1、前期准备
  1. byte*           write_buf;
  2. lsn_t           write_lsn;
  3. ut_ad(!srv_read_only_mode);
  4. if (recv_no_ibuf_operations) {
  5.         return;
  6. }
  7. loop:
  8.         ut_ad(++loop_count < 128);
复制代码
  
  2、条件判断
  1.         log_write_mutex_enter();
  2.         ut_ad(!recv_no_log_write);
  3.         lsn_t        limit_lsn = flush_to_disk
  4.                 ? log_sys->flushed_to_disk_lsn
  5.                 : log_sys->write_lsn;
  6.         if (limit_lsn >= lsn) {
  7.                 log_write_mutex_exit();
  8.                 return;
  9.         }
复制代码
  
  1. if (log_sys->n_pending_flushes > 0
  2.         || !os_event_is_set(log_sys->flush_event)) {
  3.         log_write_mutex_exit();
  4.         os_event_wait(log_sys->flush_event);
  5.         goto loop;
  6. }
复制代码
  
  1. if (flush_to_disk
  2.         && (log_sys->n_pending_flushes > 0
  3.         || !os_event_is_set(log_sys->flush_event))) {
  4.         /* Figure out if the current flush will do the job
  5.         for us. */
  6.         bool work_done = log_sys->current_flush_lsn >= lsn;
  7.         log_write_mutex_exit();
  8.         os_event_wait(log_sys->flush_event);
  9.         if (work_done) {
  10.                 return;
  11.         } else {
  12.                 goto loop;
  13.         }
  14. }
  15. log_mutex_enter();
  16. if (!flush_to_disk
  17.         && log_sys->buf_free == log_sys->buf_next_to_write) {
  18.         /* Nothing to write and no flush to disk requested */
  19.         log_mutex_exit_all();
  20.         return;
  21. }
复制代码
  
  3、日志写入准备
  1. log_group_t*        group;  // 指向日志组的指针
  2. ulint                start_offset;  // 日志缓冲区中待写入数据的起始偏移量
  3. ulint                end_offset;    // 日志缓冲区中待写入数据的结束偏移量
  4. ulint                area_start;    // 对齐后的待写入区域的起始偏移量
  5. ulint                area_end;      // 对齐后的待写入区域的结束偏移量
  6. ulong                write_ahead_size = srv_log_write_ahead_size;   // 写前日志大小
  7. ulint                pad_size;   // 填充大小
  8. DBUG_PRINT("ib_log", ("write " LSN_PF " to " LSN_PF,
  9.                                 log_sys->write_lsn,
  10.                                 log_sys->lsn));
  11. if (flush_to_disk) {
  12.         log_sys->n_pending_flushes++;   // 增加挂起的刷新操作计数
  13.         log_sys->current_flush_lsn = log_sys->lsn;  // 记录当前正在刷新的日志序列号
  14.         MONITOR_INC(MONITOR_PENDING_LOG_FLUSH);
  15.         os_event_reset(log_sys->flush_event);
  16.         if (log_sys->buf_free == log_sys->buf_next_to_write) {
  17.                 /* Nothing to write, flush only */
  18.                 log_mutex_exit_all();
  19.                 log_write_flush_to_disk_low();
  20.                 return;
  21.         }
  22. }
  23. /* 计算待写入数据的起始和结束偏移量,以及对齐偏移量 */
  24. start_offset = log_sys->buf_next_to_write;
  25. end_offset = log_sys->buf_free;
  26. area_start = ut_calc_align_down(start_offset, OS_FILE_LOG_BLOCK_SIZE);
  27. area_end = ut_calc_align(end_offset, OS_FILE_LOG_BLOCK_SIZE);
  28. ut_ad(area_end - area_start > 0);
  29. log_block_set_flush_bit(log_sys->buf + area_start, TRUE);
  30. log_block_set_checkpoint_no(
  31.         log_sys->buf + area_end - OS_FILE_LOG_BLOCK_SIZE,
  32.         log_sys->next_checkpoint_no);
  33. write_lsn = log_sys->lsn;
  34. write_buf = log_sys->buf;
  35. log_buffer_switch();   // 切换缓冲区
  36. group = UT_LIST_GET_FIRST(log_sys->log_groups);  // 从日志系统的日志组列表中获取第一个日志组
  37. log_group_set_fields(group, log_sys->write_lsn);  // 设置日志组的字段,如当前的写入日志序列号
  38. log_mutex_exit();
  39. /* Calculate pad_size if needed. */
  40. pad_size = 0;
  41. /* 如果写前日志大小大于操作系统日志块大小,则需要计算填充大小 */
  42. if (write_ahead_size > OS_FILE_LOG_BLOCK_SIZE) {
  43.         lsn_t        end_offset;
  44.         ulint        end_offset_in_unit;
  45.         end_offset = log_group_calc_lsn_offset(
  46.                 ut_uint64_align_up(write_lsn,
  47.                                         OS_FILE_LOG_BLOCK_SIZE),
  48.                 group);
  49.         end_offset_in_unit = (ulint) (end_offset % write_ahead_size);
  50.         if (end_offset_in_unit > 0
  51.                 && (area_end - area_start) > end_offset_in_unit) {
  52.                 /* The first block in the unit was initialized
  53.                 after the last writing.
  54.                 Needs to be written padded data once. */
  55.                 pad_size = write_ahead_size - end_offset_in_unit;
  56.                 if (area_end + pad_size > log_sys->buf_size) {
  57.                         pad_size = log_sys->buf_size - area_end;
  58.                 }
  59.                 ::memset(write_buf + area_end, 0, pad_size);
  60.         }
  61. }
复制代码
  
  4、写入日志文件
  1. /* 这行代码将缓冲区中从area_start到area_end + pad_size的数据写入到指定的日志组中。
  2. * 写入的起始日志序列号是对log_sys->write_lsn向下对齐到OS_FILE_LOG_BLOCK_SIZE的结果,而偏移量则是start_offset - area_start,表示数据在缓冲区中的相对位置。 */
  3. log_group_write_buf(
  4.         group, write_buf + area_start,
  5.         area_end - area_start + pad_size,
  6.         ut_uint64_align_down(log_sys->write_lsn,
  7.                                         OS_FILE_LOG_BLOCK_SIZE),
  8.         start_offset - area_start);
  9. srv_stats.log_padded.add(pad_size);  // 将填充大小pad_size添加到日志系统的填充统计中,有助于监控和调试日志写入过程中的填充情况。
  10. log_sys->write_lsn = write_lsn;  // 更新日志系统的写入序列号write_lsn
  11. /* 在非windowa平台上,将已刷新到磁盘的序列号flushed_to_disk_lsn更新为当前的写入序列号write_lsn */
  12. #ifndef _WIN32
  13.         if (srv_unix_file_flush_method == SRV_UNIX_O_DSYNC) {
  14.                 log_sys->flushed_to_disk_lsn = log_sys->write_lsn;
  15.         }
  16. #endif /* !_WIN32 */
  17. log_write_mutex_exit();
  18. if (flush_to_disk) {
  19.         log_write_flush_to_disk_low();  // 如果flush_to_disk为真,则调用log_write_flush_to_disk_low函数将日志数据真正刷新到磁盘上。这是确保数据持久化的关键步骤。
  20. }
复制代码
  

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/) Powered by Discuz! X3.4