由于内存数据写入磁盘的开销很大,如果频繁 fsync() 把日志数据永久写入磁盘,数据库的性能将会急剧下降。为此提供 sync_binlog 参数来设置多少个 binlog 日志产生的时候调用一次 fsync() 把二进制日志刷入磁盘来提高整体性能,该参数的设置作用为:
sync_binlog=0,二进制日志 fsync() 的操作基于系统自动执行。
sync_binlog=1,每次事务提交都会调用 fsync(),最大限度保证数据安全,但影响性能。
sync_binlog=N,当数据库崩溃时,可能会丢失 N-1 个事务。
prepare_commit_mutex 的锁机制会严重影响高并发时的性能,而且 binlog 也无法执行组提交。
改进方案接下来,看看如何保证 binlog 写入顺序和存储引擎提交顺序是一致的,并且能够进行 binlog 的组提交?5.6 引入了组提交,并将提交过程分成 Flush stage、Sync stage、Commit stage 三个阶段。
这样,事务提交时分为了如下的阶段:
InnoDB, PrepareSQL已经成功执行并生成了相应的redo和undo内存日志;
Binlog, Flush Stage
所有已经注册线程都将写入binlog缓存;
Binlog, Sync Stage
binlog缓存将sync到磁盘,sync_binlog=1时该队列中所有事务的binlog将永久写入磁盘;
InnoDB, Commit stage
leader根据顺序调用存储引擎提交事务;
每个 Stage 阶段都有各自的队列,从而使每个会话的事务进行排队,提高并发性能。
如果当一个线程注册到一个空队列时,该线程就做为该队列的 leader,后注册到该队列的线程均为 follower,后续的操作,都由 leader 控制队列中 follower 行为。
leader 同时会带领当前队列的所有 follower 到下一个 stage 去执行,当遇到下一个 stage 为非空队列时,leader 会变成 follower 注册到此队列中;注意:follower 线程绝不可能变成 leader 。
配置参数与 binlog 组提交相关的参数主要包括了如下两个参数。
binlog_max_flush_queue_time单位为微妙,用于从 flush 队列中取事务的超时时间,这主要是防止并发事务过高,导致某些事务的 RT 上升,详细的内容可以查看函数 MYSQL_BIN_LOG::process_flush_stage_queue() 。
注意:该参数在 5.7 之后已经取消了。
binlog_order_commits当设置为 0 时,事务可能以和 binlog 不同的顺序提交,其性能会有稍微提升,但并不是特别明显.
源码解析binlog 的组提交是通过 Stage_manager 管理,其中比较核心内容如下。
class Stage_manager {public:
enum StageID { // binlog的组提交包括了三个阶段
FLUSH_STAGE,
SYNC_STAGE,
COMMIT_STAGE,
STAGE_COUNTER
};
private:
Mutex_queue m_queue[STAGE_COUNTER];
};
组提交 (Group Commit) 三阶段流程,详细实现如下。
MYSQL_BIN_LOG::ordered_commit() ← 执行事务顺序提交,binlog group commit的主流程|
|-#########>>>>>>>>> ← 进入Stage_manager::FLUSH_STAGE阶段
|-change_stage(..., &LOCK_log)
| |-stage_manager.enroll_for() ← 将当前线程加入到m_queue[FLUSH_STAGE]中
| |
| | ← (follower)返回true
| |-mysql_mutex_lock() ← (leader)对LOCK_log加锁,并返回false
|
|-finish_commit() ← (follower)对于follower则直接返回
| |-ha_commit_low()
|
|-process_flush_stage_queue() ← (leader)对于follower则直接返回
| |-fetch_queue_for() ← 通过stage_manager获取队列中的成员
| | |-fetch_and_empty() ← 获取元素并清空队列
| |-ha_flush_log()
| |-flush_thread_caches() ← 对于每个线程做该操作
| |-my_b_tell() ← 判断是否超过了max_bin_log_size,如果是则切换binlog文件
|
|-flush_cache_to_file() ← (follower)将I/O Cache中的内容写到文件中
|-RUN_HOOK() ← 调用HOOK函数,也就是binlog_storage->after_flush()
|
|-#########>>>>>>>>> ← 进入Stage_manager::SYNC_STAGE阶段
|-change_stage()
|-sync_binlog_file()
| |-mysql_file_sync()
| |-my_sync()
| |-fdatasync() ← 调用系统API写入磁盘,也可以是fsync()
|
|-#########>>>>>>>>> ← 进入Stage_manager::COMMIT_STAGE阶段
|-change_stage() ← 该阶段会受到binlog_order_commits参数限制
|-process_commit_stage_queue() ← 会遍厉所有线程,然后调用如下存储引擎接口
| |-ha_commit_low()
| |-ht->commit() ← 调用存储引擎handlerton->commit()
| | ← ### 注意,实际调用如下的两个函数
| |-binlog_commit()
| |-innobase_commit()
|-process_after_commit_stage_queue() ← 提交之后的后续处理,例如semisync
| |-RUN_HOOK() ← 调用transaction->after_commit
|
|-stage_manager.signal_done() ← 通知其它线程事务已经提交
|
|-finish_commit()