随笔记录,比较凌乱
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