Innodb log sys 相关的一些随笔

随笔记录,比较凌乱

A.log_sys的几个和log buffer相关的变量

buf_size:

     log_sys->buf_size = LOG_BUFFER_SIZE;   

     LOG_BUFFER_SIZE为srv_log_buffer_size * UNIV_PAGE_SIZE, 也就是变量innodb_log_buffer_size的大小

buf_free
       指向当前在log buf中可写入的起始偏移量

max_buf_free

        log_sys->max_buf_free = log_sys->buf_size / LOG_BUF_FLUSH_RATIO
                – LOG_BUF_FLUSH_MARGIN;

#define LOG_BUF_WRITE_MARGIN    (4 * OS_FILE_LOG_BLOCK_SIZE) 

#define LOG_BUF_FLUSH_RATIO     2

#define LOG_BUF_FLUSH_MARGIN    (LOG_BUF_WRITE_MARGIN + 4 * UNIV_PAGE_SIZE)

log_sys->max_buf_free主要用于:

1.当当前已经写入文件的位置(log_sys->write_end_offset)在buf中的log_sys/max_buf_free的二分之一时,也就是差不多1/4的log buffer的位置时,会做一次memmove,将从log_sys->write_end_offset到log_sys->buf_free之间以512字节的block为单位进行memmove到log buf的起始部分。同时修改log_sys->buf_free 和log_sys->buf_next_to_write这两个变量向前移动。

2.在写入一个mtr log后(log buf中最后一个未写满的block无法放入mtr log时),调用log_close时,会去看log_sys->buf_free是否大于max_buf_free
如果大于,则将log_sys->check_flush_or_checkpoint设置为TRUE,这可能会影响到DML操作检查redo空间的行为(log_free_check)

check_flush_or_checkpoint
    当这个变量设置为TRUE时,就是告诉用户线程,这时候log buf 可能太小了,需要去做一次flush,另外也会检查log file是否已经到某个临界点了(async/sync),可能需要去刷脏页,具体见函数log_free_check

buf_next_to_write
     下一次开始写入文件的位置,当需要将buf写文件时,这个变量用于决定写入的起始位置,及起始对齐的block。
      该变量的判断及赋值主要在log_write_up_to函数中

write_end_offset
      在准备将buf写入文件时,最后一个预备写入的Block的数据会向后拷贝一个block(512字节),write_end_offset指向新的block中数据的偏移量位置

write_lsn:
       写入的lsn,每次在准备将log buf的数据拷贝到文件之前,将其设置为log_sys->write_lsn = log_sys->lsn;
        因此write_lsn总是>= written_to_some_lsn 和written_to_all_lsn,前者可能已经设置了,但还没写入到该lsn,后两者表示已经写到文件的lsn

written_to_some_lsn
      在完成一次写入后,将其值设置为write_lsn,表示当前写入文件的lsn (log_group_check_flush_completion)
       在进入log_write_up_to函数时会根据该值判断是否需要进行写入 
     
written_to_all_lsn
       和written_to_some_lsn类似,同样在完成写入后,将其设置为write_lsn

current_flush_lsn
       和write_lsn类似,在将buf写入文件之前,如果flush_to_disk为true,则将其设置为log_sys->lsn;可以理解为当前正在flush的lsn,他的值总是>= flushed_to_disk_lsn

flushed_to_disk_lsn
        在完成写入后,会尝试将文件刷到磁盘(如果flush_to_disk为TRUE),完成后,将flushed_to_disk_lsn设置为当前写到的lsn(log_sys->write_lsn)

n_pending_writes
        等于时,表示当前已经有拷贝buf到文件正在进行,这种情况下,不允许同时发生写入操作,其他需要等待

one_flushed
     在开始将log buf写到文件之前设置为FALSE,在写入完成,以及完成fsync(如果需要)后,再调用函数log_group_check_flush_completion将其设置为TRUE.

 

B.
除了redo真正的日志外,每个写入磁盘的数据是以block为单位的,一个block默认为512字节,在block头部,有12个字节来维护该block的相关信息(LOG_BLOCK_HDR_SIZE),主要包括:

LOG_BLOCK_HDR_NO

该block的block no,每次初始化一个新的block时

会去计算,根据当前log_sys->lsn计算,保持递增

LOG_BLOCK_HDR_DATA_LEN

当前block中redo log的长度,包括头部12字节

LOG_BLOCK_FIRST_REC_GROUP

当该值为0时,表示这个block为一个mtr日志的一部分

否则,该值为该block中的第一个mtr的偏移量

LOG_BLOCK_CHECKPOINT_NO

在写满一个block/写buf到文件时,设置该值为当前

log_sys->next_checkpoint_no

      
 

C 拷贝mlog到log buf中

mtr_commit->mtr_log_reserve_and_write

有两种拷贝日志到buf的场景:

a.首先判断mlog->heap == NULL,这时候认为日志较小
对于比较小的log,进入函数log_reserve_and_write_fast
1)持有log_sys->mutex
log_sys->buf_free % OS_FILE_LOG_BLOCK_SIZE+len超过OS_FILE_LOG_BLOCK_SIZE – LOG_BLOCK_TRL_SIZE的话,
表示写入这个日志可能产生跨块(512字节),这时候释放log_sys->mutex,并直接返回

2)
否则把这个log拷贝到,推进log_sys变量:

log_sys->buf_free += len;
log_sys->lsn += len;

返回拷贝数据后的log_sys->lsn

这里的len就是日志的实际长度;

当无法进行fast copy时,就进入第二种方式

b.

首先计算日志数据的长度data_size,然后根据data_size来确定一个最大上限值,用于判断当前的buf里是否有足够的空闲空间(log_reserve_and_open(data_size))

len_upper_limit = LOG_BUF_WRITE_MARGIN + (5 * len) / 4;

其中LOG_BUF_WRITE_MARGIN为4*512字节
当剩余空间小于这个值时,就需要将buf刷到磁盘上(log_buffer_flush_to_disk();) 

这里的上限只是一个粗略的值;

遍历mtr->log,这是一个dyn_block_t数组,依次写入buf中(log_write_low),对于每一个mtr日志块:
这里可能产生多次拷贝
第一次拷贝时,如果log buf中最后一个已用的512字节的buf剩余空间无法存放这个block,就先拷贝一部分数据,将最后一个block占满(但预留4个字节)
实际上一个512字节的block中,需要首尾都预留空间,头部12字节,尾部4字节,

我们假定最后一个512字节的log buf 块中还剩下100字节可用, 即log_sys->buf_free%OS_FILE_LOG_BLOCK_SIZE = 412
假定这时候log_sys->lsn = 0; 我们要写入的日志块大小为1000字节

第一轮:
拷贝长度, len = 512-412-4 = 96字节
由于该buf block已经满了,所以我们推进lsn到下一个block,并跳过下一个block的头部12个保留字节

log_sys->lsn = 96+4+12 = 112

log_sys->buf_free也推进到下一个block的头部位置

日志数据剩余长度为1000-96=904字节

第二轮:
拷贝长度, len = 512-12-4=496字节
该block拷满,同样推进到下一个block

log_sys->lsn = 112 + 500 + 12 = 624

log_sys->buf_free %OS_FILE_LOG_BLOCK_SIZE = 12

剩余字节为904-496 =  408

第三轮

由于可用长度为512-12-4=496大于408,因此该block可以完成拷贝,拷贝数据长度为408

log_sys->lsn = 624+408 =  1032

log_sys->buf_free %OS_FILE_LOG_BLOCK_SIZE = 12+408 = 420

可见,我们并不能单独的计算一个日志块占用的block size,因为log_sys->buf_free的值可能被其他线程占用;

但我们可以占用log_sys->mutex并快速计算出占用log buf的长度

buf_free%OS_FILE_LOG_BLOCK_SIZE

a = 512 – buf_free%512 – 4 

if (data_size > a)
do
   n = (data_size -a )/(512-4-12) = (data_size -a )/496
   extra = (data_size -a )%496
done

len = (512 – buf_free%512) + n*512 +   extra +12

如上例,

a = 512-412-4 = 96

n = (1000-96)/496 = 1

extra = (1000-96)%496 = 408

len = (512-412) + 1*512 + 408 +12 = 1032

注意在拷贝数据的过程中,会去设置一个block的长度,在block头的第4位会设置这个block的长度,例如:
301         log_block = static_cast<byte*>(

302                 ut_align_down(

303                         log->buf + log->buf_free, OS_FILE_LOG_BLOCK_SIZE));

304

305         log_block_set_data_len(log_block, data_len);

拷贝完成数据后,这时候是持有log_sys->mutex的.

对于第一种拷贝方式,直接调用mtr_add_dirtied_pages_to_flush_list(mtr);
对于第二种拷贝方式,需要先调用log_close(),再调用mtr_add_dirtied_pages_to_flush_list(mtr)

mtr_close的返回值赋予mtr->end_lsn。对于一个mtr日志数组的最后占用的一个block,需要设置log_block_set_first_rec_group

由于持有log sys mutex,因此这里设置的是当前mtr在最后一个512字节block占用的长度;这部分可以并发来做

另外一个工作就是判断是否需要设置check_flush_or_checkpoint

mtr_close还会去调用buf_pool_get_oldest_modification,这里会去获取log_flush_order_mutex

在完成上述工作后,开始将脏页转移到flush list中,mtr_add_dirtied_pages_to_flush_list

这里会先获取log_flush_order_mutex 再去释放log_sys->mutex,然后将mtr修改的脏页加入到flush list中;

完成将脏页加入到lflush list后,释放log_flush_order_mutex锁。

D.将数据从buf写入到文件中

log_write_up_to是主要函数,这里只讨论拷贝buf的部分逻辑

持有log_sys->mutex

每次写入的起始点和结束点:
        start_offset = log_sys->buf_next_to_write;

end_offset = log_sys->buf_free;

        area_start = ut_calc_align_down(start_offset, OS_FILE_LOG_BLOCK_SIZE);

area_end = ut_calc_align(end_offset, OS_FILE_LOG_BLOCK_SIZE);

可见,如果需要刷buf到文件时,参数lsn只是用于判断是否已经有其他线程正在刷的LSN超过了传递的LSN,如果超过了,那么等待别的线程完成或者直接返回

决定了起始和终止的block后,设置log_sys->write_lsn或者log_sys->current_flush_lsn(如果参数flush_to_disk为TRUE时)为当前log_sys->lsn         
 
设置起始block和终止block的block头部对应位:
        log_block_set_flush_bit(log_sys->buf + area_start, TRUE);

log_block_set_checkpoint_no(

log_sys->buf + area_end – OS_FILE_LOG_BLOCK_SIZE,

log_sys->next_checkpoint_no);

将最后一个block拷贝到下一个block
        ut_memcpy(log_sys->buf + area_end,

log_sys->buf + area_end – OS_FILE_LOG_BLOCK_SIZE,

OS_FILE_LOG_BLOCK_SIZE);        log_sys->buf_free += OS_FILE_LOG_BLOCK_SIZE;

log_sys->write_end_offset = log_sys->buf_free;

将数据写入文件:
                 log_group_write_buf(

group, log_sys->buf + area_start,

area_end – area_start,

ut_uint64_align_down(log_sys->written_to_all_lsn,

OS_FILE_LOG_BLOCK_SIZE),

start_offset – area_start);

在函数log_group_write_buf中,还涉及到很多操作,例如计算每个512字节block的checksum等

完成写入后,即释放log_sys->mutex 

如果需要,做fil_flush刷磁盘操作,将日志落地

再次持有log_sys->mutex

unlock = log_group_check_flush_completion(group);
—>log_sys->written_to_some_lsn = log_sys->write_lsn;
—>log_sys->one_flushed = TRUE;

unlock = unlock | log_sys_check_flush_completion();

函数log_sys_check_flush_completion中,设置:
      a.
      log_sys->written_to_all_lsn = log_sys->write_lsn;
      log_sys->buf_next_to_write = log_sys->write_end_offset;
      b.
      当 log_sys->write_end_offset大于max_buf_free的一半时,将buffer内后面的内容整体向前挪动 
                        move_start = ut_calc_align_down(

                                log_sys->write_end_offset,

OS_FILE_LOG_BLOCK_SIZE);

move_end = ut_calc_align(log_sys->buf_free,

OS_FILE_LOG_BLOCK_SIZE);

ut_memmove(log_sys->buf, log_sys->buf + move_start,

move_end – move_start);

log_sys->buf_free -= move_start;

log_sys->buf_next_to_write -= move_start;

释放log_sys->mutex

原创文章,转载请注明: 转载自Simple Life

本文链接地址: Innodb log sys 相关的一些随笔

Post Footer automatically generated by wp-posturl plugin for wordpress.


Comments

Leave a Reply

Your email address will not be published. Name and email are required


Current month ye@r day *