-
Checkpoint原理
关于Innodb Checkpoint的原理,此处不准备介绍,推荐 How InnoDB performs a checkpoint [2]一文,作者详细讲解了Innodb的Checkpoint原理。
-
Checkpoint触发条件
-
每1S
-
若buffer pool中的脏页比率超过了srv_max_buf_pool_modified_pct = 75,则进行Checkpoint,刷脏页,flush PCT_IO(100)的dirty pages = 200
-
若采用adaptive flushing,则计算flush rate,进行必要的flush
-
-
每10S
-
若buffer pool中的脏页比率超过了70%,flush PCT_IO(100)的dirty pages
-
若buffer pool中的脏页比率未超过70%,flush PCT_IO(10)的dirty pages = 20
-
每10S,必定调用一次log_checkpoint,做一次Checkpoint
-
Innodb如何计算脏页比率?adaptive flushing时如何计算flush rate?如何进行真正的flush操作,是否使用AIO,将在以下章节中一一分析。
-
Checkpoint流程
-
计算脏页比率
-
srv0srv.c::srv_master_thread -> buf0buf.c::buf_get_modified_ratio_pct -> buf_get_total_list_len
for (i = 0; i < srv_buf_pool_instances; i++) {
buf_pool_t* buf_pool;
buf_pool = buf_pool_from_array(i);
*LRU_len += UT_LIST_GET_LEN(buf_pool->LRU);
*free_len += UT_LIST_GET_LEN(buf_pool->free);
*flush_list_len += UT_LIST_GET_LEN(buf_pool->flush_list);
}
ratio = (100 * flush_list_len) / (1 + lru_len + free_len);
脏页比率 = 需要被flush的页面数 / (使用中的页面数 + 空闲页面数 + 1)
其中,所有的值,在buf_pool_t结构中均有统计,无需实际遍历buffer pool进行计算
-
计算adaptive flush rate
函数流程:
buf0buf.c::buf_flush_get_desired_flush_rate ->
-
从buf_pool_t结构中,获得总dirty page的数量
-
计算最近一段时间之内,redo日志产生的平均速度
redo_avg = (ulint) (buf_flush_stat_sum.redo
/ BUF_FLUSH_STAT_N_INTERVAL
+ (lsn – buf_flush_stat_cur.redo));
其中,BUF_FLUSH_STAT_N_INTERVAL = 20S,20S内的平均redo产生速度
/** Number of intervals for which we keep the history of these stats.
Each interval is 1 second, defined by the rate at which
srv_error_monitor_thread() calls buf_flush_stat_update(). */
#define
BUF_FLUSH_STAT_N_INTERVAL 20
flush的统计信息,每隔20S会被buf_flush_stat_update函数重置
-
计算过去一段时间内,flush的平均速度;与当前需要的flush速度
lru_flush_avg = buf_flush_stat_sum.n_flushed
/ BUF_FLUSH_STAT_N_INTERVAL
+ (buf_lru_flush_page_count
– buf_flush_stat_cur.n_flushed);
n_flush_req = (n_dirty * redo_avg) / log_capacity;
其中,BUF_FLUSH_STAT_N_INTERVAL = 20S不变,计算的仍旧是过去20S内的平均flush速度
-
若当前所需flush的page数量 > 20S flush的平均数量,则adaptive flushing会尝试进行一次flush操作。flush的dirty pages数量最大是PCT_IO(100),200个dirty pages。
-
flush dirty pages算法
-
函数流程:
buf0flu.c::buf_flush_list -> buf_flush_start -> buf_flush_batch -> buf_flush_end -> buf_flush_common
-
首先,判断当前是否有正在进行的相同类型的flush (buf_flush_start),有则直接退出
-
buf_flush_batch -> buf_flush_flush_list_batch -> buf_flush_page_and_try_neighbors ->buf_flush_try_neighbors -> buf_flush_page -> buf_flush_buffered_writes ->
-
从flush_list的最后一个页面开始,向前遍历页面 & flush
-
对于a)中的page,尝试flush,并且尝试flush该page的neighbors pages (buf_flush_try_neighbors)
-
首先计算可选的neighbors范围。所谓neighbors范围,指的是space_id相同,page_no不同的page,只有这些page才是连续的。
buf_flush_area = ut_min(
ut_min(64, ut_2_power_up((b)->curr_size / 32)),
buf_pool->curr_size / 16);
low = (offset / buf_flush_area) * buf_flush_area;
high = (offset / buf_flush_area + 1) * buf_flush_area;
low为当前page,neighbors的范围既为buf_flush_area,最大64
neighbors为当前page开始,page_no连续递增的buf_flush_area个pages
-
所有的dirty pages,在buffer pool中同时以hash表存储,根据(space_id, [page_no_low, page_no_high])到hash表中进行查找,若存在,则flush此dirty page
-
-
-
buf_flush_page -> buf_flush_write_block_low -> log_write_up_to ->
-
flush脏页之前,必须保证脏页对应的日志已经写回日志文件(log_write_up_to)
-
判断是否需要使用double write
-
若不需要double write保护,直接调用fil_io进行flush操作,设置type = OS_FILE_WRITE;mode = OS_AIO_SIMULATED_WAKE_LATER
-
若需要double write保护,则调用buf_flush_post_to_doublewrite_buf函数
-
写到double write就算完成,退出buf_flush_page
-
-
-
-
buf_flush_batch -> buf_flush_buffered_writes
-
buf_flush_batch函数,在完成2,3步骤,batch flush之后,调用buf_flush_buffered_writes函数进行真正的write操作
-
buf_flush_buffered_writes:将double write memory写出到disk
-
我的测试中,有7个dirty pages,每个page大小为16k = 16384,因此doublewrite buffer的大小为 16384 * 7 = 114688
-
doublewrite buffer的写,为同步写,调用fil_io(OS_FILE_WRITE, TRUE)
-
同步写之后,调用fil_flush函数,将doublewrite buffer中的内容flush到disk
-
windows:
FlushFileBuffers(file);
-
linux:
os_file_fsync(file); or
fcntl(file, F_FULLFSYNC, NULL);
-
-
在doublewrite buffer被成功flush到disk之后,对应的dirty pages不会再丢失数据。此时再将doublewrite buffer对应的dirty pages写出到disk
-
fil_io(OS_FILE_WRITE | OS_AIO_SIMULATED_WAKE_LATER, FALSE);
-
写dirty pages,采用非同步写 AIO
-
-
在dirty pages都完成异步IO之后,调用buf_flush_sync_datafiles函数,将所有的异步IO操作,flush到磁盘
-
/* Wake simulated aio thread to actually post the writes to the operating system */
os_aio_simulated_wake_handler_threads();
/* Wait that all async writes to tablespaces have been posted to the OS */
os_aio_wait_until_no_pending_writes();
/* Now we flush the data to disk (for example, with fsync) */
fil_flush_file_spaces(FIL_TABLESPACE);
-
-
标识当前flush操作结束(buf_flush_end)
-
收集当前flush操作的统计信息(buf_flush_common)
-
Checkpoint info更新
-
在完成Checkpoint流程中的flush dirty pages之后,Innodb Checkpoint的大部分流程已经完成,只余下最后的修改Checkpoint Info信息。
-
流程一
更新Checkpoint Info流程一,流程一每10S调用一次:
srv_master_thread -> log0log.c::log_checkpoint ->
log_buf_pool_get_oldest_modification ->
-
读取系统中,最老的日志序列号。实现简单,读取lsn flush list中最老日志对应的lsn即可
log_write_up_to(oldest_lsn, LOG_WAIT_ALL_GROUPS, TRUE) ->
-
将日志flush到oldest_lsn
log_groups_write_checkpoint_info -> log_group_checkpoint ->
fil_io(OS_FILE_WRITE | OS_FILE_LOG, FALSE) ->
-
遍历所有日志组,分别更新每个日志组对应的Checkpoint Info
-
构造Checkpoint Info,使用os_aio_log_array进行异步写I/O操作
-
流程二
更新Checkpoint Info流程二,在I/O较为繁忙的系统中,流程二每1S调用一次:
srv_master_thread -> log0log.ic::log_free_check -> log0log.c::log_check_margins ->
log_checkpoint_margin -> log_preflush_pool_modified_pages -> log_checkpoint
-
读取当前日志系统中的最老日志序列号lsn
根据oldest_lsn与log->lsn(current lsn)之间的差距,判断日志空间是否足够,是否需要进行flush dirty pages操作
-
读取当前日志系统中最老的Checkpoint Lsn
根据last_checkpoint_lsn与log->lsn之间的差距,判断是否需要向前推进检查点
-
若需要flush dirty pages,调用函数log_preflush_pool_modified_pages
log_preflush_pool_modified_pages -> buf_flush_list(ULINT_MAX, new_oldest)
-
new_oldest参数,指定当前将dirty pages flush到何lsn?sync参数指定当前flush操作是否为同步操作?由函数log_checkpoint_margin计算,代码如下:
oldest_lsn = log_buf_pool_get_oldest_modification();
age = log->lsn – oldest_lsn;
if (age > log->max_modified_age_sync) {
/* A flush is urgent: we have to do a synchronous preflush */
sync = TRUE;
advance = 2 * (age – log->max_modified_age_sync);
} else
if (age > log->max_modified_age_async) {
/* A flush is not urgent: we do an asynchronous preflush */
advance = age – log->max_modified_age_async;
} else {
advance = 0;
}
ib_uint64_t new_oldest = oldest_lsn + advance;
-
if (checkpoint_age > log->max_checkpoint_age) {
/* A checkpoint is urgent: we do it synchronously */
checkpoint_sync = TRUE;
do_checkpoint = TRUE;
}
-
log->max_modified_age_(a)sync; log->max_checkpoint_age
以上参数用于控制是否需要进行log flush,以及是否需要进行Checkpoint。
参数的计算,在log0log.c::log_calc_max_ages函数中完成,代码较为简单,如下所示:
margin = smallest_capacity – free;
margin = margin – margin / 10; /* Add still some extra safety */
log->log_group_capacity = smallest_capacity;
log->max_modified_age_async = margin–margin / LOG_POOL_PREFLUSH_RATIO_ASYNC;
log->max_modified_age_sync = margin – margin / LOG_POOL_PREFLUSH_RATIO_SYNC;
log->max_checkpoint_age_async = margin–margin/LOG_POOL_CHECKPOINT_RATIO_ASYNC;
log->max_checkpoint_age = margin;
#define
LOG_POOL_CHECKPOINT_RATIO_ASYNC 32
#define
LOG_POOL_PREFLUSH_RATIO_SYNC 16
#define
LOG_POOL_PREFLUSH_RATIO_ASYNC 8
简单来说,margin近似认为是Innodb系统可用的日志空间的9/10;
日志空间消耗超过7/8时,一定要进行异步Flush日志;
日志空间消耗超过15/16时,一定要进行同步Flush日志;
日志空间消耗超过31/32时,一定要进行异步Flush Buffer Pool;
日志空间消耗达到margin上限时,一定要进行同步Flush Buffer Pool
以上判断均在log_checkpoint_margin函数中完成,1S中判断一次。
-
若需要向前推进检查点,调用函数log_checkpoint,log_checkpoint函数的流程,在前一章节中已经分析。
-
innodb_flush_method
无论是数据文件,还是日志文件,在完成write操作之后,最后都需要flush到disk。
是否flush?如何进行flush?日志文件与数据文件的flush操作有何不同?通过参数innodb_flush_method控制。
关于innodb_flush_method这个参数的意义及设置,网上有大量的文档。具体可参考 innodb_flush_method与File I/O 与 SAN vs Local-disk::innodb_flush_method performance benchmarks 两文。
接下来我主要从源码层面简单分析以下innodb_flush_method参数的使用(在innodb 5.5-5.6中,此参数的名字修改为innobase_file_flush_method)。
-
初始化
函数处理流程:
srv0start.cc::innobase_start_or_create_for_mysql
if (srv_file_flush_method_str == NULL) {
/* These are the default options */
srv_unix_file_flush_method = SRV_UNIX_FSYNC;
srv_win_file_flush_method = SRV_WIN_IO_UNBUFFERED;
} else if (0 == ut_strcmp(srv_file_flush_method_str, “fsync”)) {
srv_unix_file_flush_method = SRV_UNIX_FSYNC;
} else if (0 == ut_strcmp(srv_file_flush_method_str, “O_DSYNC”)) {
srv_unix_file_flush_method = SRV_UNIX_O_DSYNC;
} else if (0 == ut_strcmp(srv_file_flush_method_str, “O_DIRECT”)) {
srv_unix_file_flush_method = SRV_UNIX_O_DIRECT;
} else if (0 == ut_strcmp(srv_file_flush_method_str, “littlesync”)) {
srv_unix_file_flush_method = SRV_UNIX_LITTLESYNC;
} else if (0 == ut_strcmp(srv_file_flush_method_str, “nosync”)) {
srv_unix_file_flush_method = SRV_UNIX_NOSYNC;
}
根据用户指定的srv_file_flush_method_str的不同,设置srv_unix_file_flush_method的不同取值,innodb内部,通过判断此参数,来确定以何种模式open file,以及是否flush write。
简单起见,此处只拷贝了linux部分处理代码,未包括windows部分。
-
open file
Innodb系统启动阶段,设置完成srv_unix_file_flush_method参数之后,可以进行I/O操作,I/O操作的总入口为函数fil0fil.c::fil_io,相信大家已经看过上面的分析之后,对此函数不会陌生。
fil0fil.c::fil_io函数中,处理了file open的过程,函数流程如下:
fil0fil.c::fil_io -> fil_node_perpare_for_io -> fil_node_open_file ->
if (space->purpose == FIL_LOG) {
node->handle = os_file_create(innodb_file_log_key, node->name, OS_FILE_OPEN,
OS_FILE_AIO, OS_LOG_FILE, &ret);
} else
if (node->is_raw_disk) {
node->handle = os_file_create(innodb_file_data_key, node->name,
OS_FILE_OPEN_RAW, OS_FILE_AIO, OS_DATA_FILE, &ret);
} else {
node->handle = os_file_create(innodb_file_data_key, node->name, OS_FILE_OPEN,
OS_FILE_AIO, OS_DATA_FILE, &ret);
}
根据当前文件类型不同,底层依赖的硬件环境不同,调用os_file_create宏定义open对应的文件。os_file_create宏定义对应的函数是os_file_create_func.
os_file_create_func函数处理open file的流程:
-
Log file将O_DSYNC转化为O_SYNC,O_DSYNC设置只对data file有用
if (type == OS_LOG_FILE && srv_unix_file_flush_method == SRV_UNIX_O_DSYNC) {
create_flag = create_flag | O_SYNC;
file = open(name, create_flag, os_innodb_umask);
-
Data file与O_DIRECT组合,需要禁用底层os file cache
/* We disable OS caching (O_DIRECT) only on data files */
if (type != OS_LOG_FILE && srv_unix_file_flush_method == SRV_UNIX_O_DIRECT)
os_file_set_nocache(file, name, mode_str);
-
flush data
fil0fil.c::fil_io函数打开file之后,可以进行file的write与必要的flush操作,write操作在前面的章节中已经分析,本章主要看srv_unix_file_flush_method参数对于flush操作的影响。
-
srv_unix_file_flush_method = SRV_UNIX_NOSYNC
无论是log file,还是data file,一定只write,但不flush
-
srv_unix_file_flush_method = SRV_UNIX_O_DSYNC
Log file: 不flush
if (srv_unix_file_flush_method != SRV_UNIX_O_DSYNC
&& srv_unix_file_flush_method != SRV_UNIX_NOSYNC)
fil_flush(group->space_id);
当然,其他情况下,Log file是否一定flush?还与参数srv_flush_log_at_trx_commit的设置有关
Data file:
-
srv_unix_file_flush_method = SRV_UNIX_LITTLESYNC
Data file: 不flush
-
srv_unix_file_flush_method =
-
未明之处
innodb_flush_method参数,在使用系统native aio时,好像对于data file完全无影响,还需要进一步的理解与调研。
-
Percona版本优化
关于Percona XtraDB的优化,推荐一篇十分好的文章:XtraDB: The Top 10 enhancements [11]. 该文详细列举了XtraDB对于原生InnoDB引擎做的最重要的10个优化,虽然文章是2009年8月写的,但是主要优化都已经存在了,每一个都值得一读。
当然,在本文中,我接下来主要讨论XtraDB在Checkpoint与Insert Buffer两个方面做的优化。Checkpoint与Insert Buffer优化,都属于XtraDB优化中的一个大类:I/O优化,可见网文:Improved InnoDB I/O Scalability [12].
增加innodb_io_capacity选项。原生innodb中的参数srv_io_capacity,写死的是200,XtraDB中增加此选项,用户可以根据系统的硬件不同而设置不同的io_capacity。但是,io_capacity与系统的实际I/O能力还是有所区别,网文与其中的讨论:MYSQL 5.5.8 and Percona Server: being adaptive [13]给出了fusion-io下,innodb_io_capacity选项具体如何设置更为合理。
源代码,参考的版本包括Percona-Server-5.5.15-rel21.0; Percona-Server-5.5.18-rel23.0; Percona-Server-5.1.60;
-
Flush & Checkpoint优化
XtraDB对于Flush & Checkpoint的优化,主要在于新增了系统变量:innodb_adaptive_checkpoint
此变量可设置的值包括:none, reflex, estimate, keep_average,分别对应于0/1/2/3;同时改变量要与Innodb自带的innodb_adaptive_flushing变量配合使用
关于每种设置的不同含义,[12]中有详尽介绍,此处给出简单说明:
-
none
原生innodb adaptive flushing策略
-
reflex
与innodb基于innodb_max_dirty_pages_pct的flush策略类似。不同之处在于,原生innodb是根据dirty pages的量来flush;而此处根据dirty pages的age进行flush。每次flush的pages根据innodb_io_capacity计算
-
estimate
与reflex策略类似,都是基于dirty page的age来flush。不同之处在于,每次flush的pages不再根据innodb_io_capacity计算,而是根据[number of modified blocks], [LSN progress speed]和[average age of all modified blocks]计算
-
keep_average
原生Innodb每1S触发一次dirty page的flush,此参数降低了flush的时间间隔,从1S降低为0.1S
注:在最新的Percona XtraDB版本中,reflex策略已经被废弃;estimate,keep_average策略的算法,或多或少也与网文中提到的有所出入,应该是算法优化后的结果,具体算法参考以下的几个小章节。
-
reflex
此策略,Percona XtraDB 5.1.60版本中存在,但是在5.5版本中被删除,源代码级别彻底删除,可能并无太多的意义,功能与estimate重合,不建议使用。
-
estimate
函数处理流程:
// 1. innodb_adaptive_checkpoint参数必须与innodb_adaptive_flushing同时设置
if (srv_adaptive_flushing && srv_adaptive_flushing_method == 1)
// 2. 获取当前最老dirty page的lsn
oldest_lsn = buf_pool_get_oldest_modification();
// 3. 若当前未flush的日志量,超过Checkpoint_age的1/4,则进行flush
if ((log_sys->lsn) – oldest_lsn) > (log_sys->max_checkpoint_age/4)
// 4. estimate flush策略
-
遍历buffer pool的flush_list链表,统计以下信息
-
n_blocks: 链表中的page数量
-
level: 链表所有page刷新的紧迫程度的倒数
level += log_sys->max_checkpoint_age –
(lsn – oldest_modification);
最新修改的page,level贡献越大,紧迫程度越小;越老的page,紧迫程度越大。
关于log_sys->max_checkpoint_age的功能,可参考 Checkpoint Info更新-流程二 章节。
-
-
需要flush的dirty pages数量bpl,计算公式如下:
bpl = n_blocks * n_blocks * (lsn – lsn_old) / level;
其中:lsn_old为上一次flush时记录下的lsn
-
调用buf_flush_list函数,进行flush
buf_flush_list(bpl, oldest_lsn + (lsn – lsn_old));
-
keep_average
keep_average策略,将原生的Innodb,每1S flush一次dirty pages,改为每0.1S做一次。
if (srv_adaptive_flushing && srv_adpative_flushing_method == 2)
next_itr_time -= 900;
Innodb,每次将netx_itr_time加1000ms,然后sleep这1000ms时间。进入keep_average策略,将next_itr_time减去900,那么下一次也就只会sleep 100ms时间。
接下来则是分析keep_average策略如何计算当前需要flush多少dirty pages。下图能够较为清晰的说明keep_average策略:
图表 51 Percona keep_average flush策略
图中名词解释:
prev_flush_info.count
上一次flush前,buffer pool中的dirty pages数量
new_blocks_sum
上次记录prev_flush_info.count之后,系统新产生的dirty pags
blocks_sum
当前系统,buffer pool中的dirty pages数量
flushed_blocks_sum
flushed_blocks_sum = new_blocks_sum + prev_flush_info.count – blocks_sum;
上次的flush数量+循环间其余flush的数量
Next Round
本次flush dirty pages前,记录新的prev_flush_info.count = blocks_sum
n_pages_flushed_prev
此参数未标出,表示上次keep_average策略成功flush了多少dirty pages
计算本次应该flush的量:
n_flush = blocks_sum * (lsn – lsn_old) / log_sys->max_modified_age_async;
公式分析:
系统中的dirty pages,有多少比率需要在此时被flush
log_sys->max_modified_age_async
日志异步flush的临界值,若当前系统的lsn间隔大于此值,则启动异步flush。
此参数为原生Innodb所有,Percona此处借用来计算。
关于log_sys->max_modified_age_async参数在Innodb中的作用及设置,
可参考 Checkpoint Info更新-流程二 章节。
flush量微调:
if (flushed_blocks_sum > n_pages_flushed_prev)
n_flush -= (flushed_blocks_sum – n_pages_flushed_prev);
若flushed_blocks_sum > n_pages_flushed_prev,两次之间的实际flush量大于
上次keep_average的flush量,那么本次keep_average需要flush量应相应减少。
-
MySQL 5.6.6 Flushing优化
InnoDB自带的flushing策略有问题,Percona的XtraDB对其做了优化,提出了estimate与keep_average两种新的flushing策略。在MySQL 5.6.6-labs版本中,Oracle InnoDB团队提出了自己的新的flushing算法[16],不同于已有的Percona算法,以下对此新算法作简要分析。关于新flushing算法的测试结果,可见[14][15]。
首先,在MySQL 5.6-labs版本中,dirty pages的flushing操作,从InnoDB主线程中移出,新开一个buf_flush_page_cleaner_thread (page_cleaner)线程来进行。仍旧是每次休眠1s,然后进行一次flushing尝试。
-
处理流程
新的flushing算法的处理流程如下:
buf0flu.cc::buf_flush_page_cleaner_thread();
// 回收buffer pool LRU链表,此处不考虑
page_cleaner_flush_LRU_tail();
// 根据用户指定的参数,判断是否需要进行dirty pages的flushing
// 每1S调用一次此函数
page_cleaner_flush_pages_if_needed();
// 根据用户指定的innodb_flushing_avg_loops参数(默认取值为30),
// 计算前innodb_flushing_avg_loops个flushing的平均速度
// 1. avg_page_rate: 平均的dirty pages的flushing速度
// 2. lsn_avg_rate: 平均的日志产生速度
if (++n_iterations >= srv_flushing_avg_loops)
…
// 计算目前系统中的最老未flush的dirty page的日志年龄
age = cur_lsn – oldest_lsn;
// 根据系统中的脏页数量,计算本次需要flushing的页面数量:
// 1. 计算系统中脏页面占用的比率[0, 100]
// 2. 若用户未指定脏页的lower water mark – innodb_max_dirty_pages_pct_lwm
// [0, 99]默认值为50. 则采用原有的srv_max_buf_pool_modified_pct参数判断
// 超过此参数则返回100.
// 3. 若用户指定了lower water mark,并且脏页比率超过此值,则返回
// dirty_pct * 100 / (srv_max_buf_pool_modified_pct + 1);否则返回0
//
// 注意:
// 根据innodb_max_dirty_pages_pct_lwm与innodb_max_buf_pool_modified_ptc
// 两参数的设置情况,最终的返回值主要与后者相关,后者设置的越小,计算出
// 的flushing量越大,甚至会超过100 (超过innodb_io_capacity)。但是,由于后者
// 默认设置为75,因此在默认设置下,返回值的范围是[0, 132],一次flush的量,
// 并不会比innodb_io_capacity多多少。
pct_for_dirty = af_get_pct_for_dirty();
// 根据系统中最老日志的年龄,计算本次需要flushing的页面数量:
// 1. 根据系统参数innodb_adaptive_flushing_lwm,默认取值为10,区间为[0, 70]
// 计算对应的lower water mark
// lsn age: af_lwm = (srv_adaptive_flushing_lwm * log_get_capacity()) / 100
// 2. 若当前最老日志的年纪小于lower water mark age,则不需要根据日志flushing
// 3. 获得系统的异步日志刷新阀值:max_async_age,此值的具体含义可见3.4.1.2
// 章节,近似等于日志空间的7/8
// 4. 若当前系统的日志年纪小于异步刷新阀值,并且系统参数
// innodb_adaptvie_flushing未开启,则不需要根据日志flushing
// 5. 不满足以上条件,需要根据日志进行flushing,返回值pct_for_lsn的计算如下:
// lsn_age_factor = (age * 100) / max_async_age; 取值区间近似为(0, 114]
// pct_for_lsn = srv_max_io_capacity / srv_io_capacity
// * (lsn_age_factor * sqrt(lsn_age_factor)) / 7.5;
// pct_for_lsn的取值范围近似为(0, 600]
// 其中,innodb_max_io_capacity参数为硬件能够支撑的最大IOPS,默认值
// 4000,innodb_io_capacity默认值为400
// 注意:
// 1.基于日志的flushing,一次flushing的速度与日志的产生速度正相关,
//日志产生的速度越快,需要flushing的dirty pages数量也越多,速度也更快。
// 2. 基于日志的flushing,其返回值有可能超过100,也就是一次flush的量
// 超过innodb_io_capacity,甚至是超过innodb_max_io_capacity的系统参数。
// 3. 在默认参数环境下,基于日志的flush策略,其速度有可能远远大于基于
// dirty pages的策略。其主要目的是为了保证日志回收足够快,从而不至于出现
// 由于日志空间不足而导致的async甚至是sync flushing出现。
pct_for_lsn = af_get_pct_for_lsn(age);
// 真正需要flushing的页面数量,是根据脏页数量/日志年龄 计算出来的
// 本次flushing页面数量的较大值
pct_total = ut_max(pct_for_dirty, pct_for_lsn);
// 根据本次计算出来的flushing页面数量,与上次flush的平均速度,再次
// 取平均值,获得最终需要flushing的页面数量
n_pages = (PCT_IO(pct_total) + avg_page_rate) / 2;
// 当然,需要根据用户设置的innodb_max_io_capacity参数,进行一次微调
// 保证一次flushing的页面数量,不会超过硬件IO能力的处理上限
if (n_pages > srv_max_io_capacity)
n_pages = srv_max_io_capacity;
// 开始进行本次真正的flushing操作
page_cleaner_do_flush_batch();
-
新增参数
MySQL 5.6.6-labs版本中,针对新的flush策略,新增了如下几个参数,具体参数的功能及意义,在上面的流程分析中已经详细给出。
-
innodb_adaptive_flushing_lwm
-
innodb_max_dirty_pages_pct_lwm
-
innodb_max_io_capacity
-
innodb_io_capacity
-
innodb_flushing_avg_loops