一个double write buffer 有2mb, 共128个page,在mysql 5.6中, 默认有120个page用于批量刷新(如 lru flush 或者flush list flush),剩下的8个page用于单个page的flush。
在debug版本下,120是可以通过参数innodb_doublewrite_batch_size来配置的,好吧。我已经不安分的把debug宏给去掉了。
全局对象buf_dblwr, 对应结构体为buf_dblwr_t:
ib_mutex_t
mutex
互斥量
ulint
block1
第一个doubewrite 块(64个page)的page no
block2
第二个double write 块的page no
first_free
在write_buf中第一个空闲的位置
s_reserved
为单个page刷新预留的slot数,当flush类型为buf_flush_single_page时,会进入到函数buf_dblwr_write_single_page来写一个page
b_reserved
为batch flush 预留的slot数
ibool*
in_use
用于标记一个slot是否被使用,只用于single page flush
ibool
batch_running
当设置为true时,表明有一次batch flush正在进行
byte*
write_buf
double write buffer在内存的缓存,以univ_page_size对其
write_buf_unaligned
未对齐的write_buf
buf_page_t**
buf_block_arr
存储已经cache到write_buf的block的指针
这里解释一下flush操作的类型,总的来说,有三种刷新类型
buf_flush_lru:表示从buffer pool的lru上扫描并刷新
buf_flush_list:表示从buffer pool的flush list上扫描并刷新
buf_flush_single_page:从lru上只刷新一个page
前两种属于batch flush, 最后一种属于single flush
buf_flush_single_page在几种情况下使用到:
1.buf_flush_or_remove_page
2.buf_flush_single_page_from_lru,这在free list不够用时,io-bound场景下,可能频繁调用到(buf_lru_get_free_block)
3.buf_flush_page_try
todo:下一篇博客再介绍这几种刷page的类型
quoted code in buf_flush_write_block_low
938 if (!srv_use_doublewrite_buf || !buf_dblwr) {
939 fil_io(os_file_write | os_aio_simulated_wake_later,
940 false, buf_page_get_space(bpage), zip_size,
941 buf_page_get_page_no(bpage), 0,
942 zip_size ? zip_size : univ_page_size,
943 frame, bpage);
944 } else if (flush_type == buf_flush_single_page) {
945 buf_dblwr_write_single_page(bpage);
946 } else {
947 buf_dblwr_add_to_batch(bpage);
948 }
这里根据flush的类型来判断对应的逻辑,如果刷新类型为buf_flush_single_page,则调用buf_dblwr_write_single_page,否则调用buf_dblwr_add_to_batch
a.
buf_dblwr_write_single_page(page):用于将一个page加入到double write buffer中,并完成写操作
主要流程如下:
1. 最大为单个page刷新预留的dblwr page数为: 2 * trx_sys_doublewrite_block_size-srv_doublewrite_batch_size 默认为8个page 当s_reserved值等于最大page数量时,线程会去sleep后retry 2.从srv_doublewrite_batch_size开始,找到一个buf_dblwr->in_use[i] 不为true的slot。分配给当前page 将in_use[i]设为true,并递增s_reserved++ 3.将单个page写入到double write buffer中。注意,这里已经释放了buf_dblwr->mutex 4.刷double write buffer (fil_flush(trx_sys_space)),然后再将数据写入到磁盘中(buf_dblwr_write_block_to_datafile,buf_flush_sync_datafiles) 注意在函数buf_flush_sync_datafiles中会唤醒io线程,等待写入完成,因此这是同步写操作 5.再次持有buf_dblwr->mutex,将s_reserved–, in_use[i], buf_block_arr[i]置为null。
b.
buf_dblwr_add_to_batch(bpage):将一个page加入到double write buffer中,如果batch满了,则刷double write buffer到磁盘
该函数的逻辑如下:
1.持有buf_dblwr->mutex 2.如果batch_running为true,表示已经有人在开始做batch flush来刷dblwr,释放互斥锁,sleep一段时间后,回到1retry 3.如果batch满了(即buf_dblwr->first_free == srv_doublewrite_batch_size),则释放mutex, 主动调用buf_dblwr_flush_buffered_writes()把dblwr的写到磁盘,然后回到1 retry 4. 将page拷贝到第buf_dblwr->first_free个槽位,并设置: buf_dblwr->buf_block_arr[buf_dblwr->first_free] = page; buf_dblwr->first_free++; buf_dblwr->b_reserved++; 5.再次检查batch flush的槽位是否已满(buf_dblwr->first_free == srv_doublewrite_batch_size) ,如果满了,则释放mutex,进行一次flush(buf_dblwr_flush_buffered_writes()),从函数返回 6.最后释放mutex.
c.
buf_dblwr_flush_buffered_writes() : 批量刷double write buffer的函数(只涉及batch flush的page)
流程如下:
2.如果fist_free为0,表示当前double write buffer为空,释放mutex,返回 3.如果batch_running为true,表示正有线程在做batch flush,释放mutex, sleep一段时间后回到1 retry 4.将batch_running设置为true,避免并发写 5.释放buf_dblwr->mutex 6.检查每一个将要写dblwr的block以及write_buf中的page是否被损坏或者lsn值是否正确 buf_dblwr_check_block(block) 以及 buf_dblwr_check_page_lsn(write_buf + len2) 7.将write_buf中的page写入到文件中 先写第一个block(<=64个page),再写第二个block(<=64个page), 也就是说最多一次写入1m, 分两次写的原因是?? 8.将double write buffer刷到磁盘后(fil_flush(trx_sys_space);),逐个开始写数据文件(os_aio_simulated_wake_later) 866 buf_dblwr_write_block_to_datafile( 867 buf_dblwr->buf_block_arr[i]); 9.唤醒io线程(os_aio_simulated_wake_handler_threads)
注意这里,在函数结束时并没有将batch_running设置为false,因为这里对数据文件做的是异步写,设置标记位的工作留给了io线程来完成
io_handler_thread-> fil_aio_wait-> buf_page_io_complete->buf_flush_write_complete->buf_dblwr_update():
看起来在double write buffer中,batch flush 和singer page flush对应的slot非常独立,那么我们是否可以把这个mutex拆成两个呢?
简单的测试了一把,结果是。。。。。差别不大
我把自己的想法提到buglist上了,不知道官方的怎么看。。。