MySQL无锁化WAL系统那些事儿( 二 )


同时,创建完成后会将数组的每个成员初始化为0 。
mtr log拷贝完成mtr在commit时会将其运行时产生的所有redo log拷贝至Innodb全局的redo log buffer,这借助了 mtr_write_log_t 对象来完成,且每次拷贝按照block为单位进行 。需要说明的是:一个mtr中可能存在多个block来存储mtr运行时产生的redo log,每个block拷贝完成后均触发一次Link_buf的更新 。
struct mtr_write_log_t {bool operator()(const mtr_buf_t::block_t *block) {...// 拷贝完成后触发LinkBuf更新log_buffer_write_completed(*log_sys, m_handle, start_lsn, end_lsn);}}?void log_buffer_write_completed(log_t &log, const Log_handle &handle,lsn_t start_lsn, lsn_t end_lsn) {...// 更新本次写入的内容范围对应的LinkBuf内特定的数组项值log.recent_written.add_link(start_lsn, end_lsn);}?template <typename Position>inline size_t Link_buf<Position>::slot_index(Position position) const {return position & (m_capacity - 1);}?template <typename Position>inline void Link_buf<Position>::add_link(Position from, Position to) {// 定位本次写入的内容范围所在数组项index// 算法是将起始lsn(@from)对数组容量取模,即from % capacityconst auto index = slot_index(from);auto &slot = m_links[index];slot.store(to - from);}在这里会找到start_lsn对应的slot,并在该slot内设置值为end_lsn - start_lsn,记录该位置处已写入的内容数量 。
log_advance_ready_for_write_lsn
Innodb将redo log buffer内容写入日志文件时需要保证不能存在空洞,即在写入前需要获得当前最大的无空洞lsn 。这同样依赖LinkBuf 。在后台写日志线程 log_writer 的 log_advance_ready_for_write_lsn 函数中完成 。
void log_writer(log_t *log_ptr) {...for (uint64_t step = 0;; ++step) {(void)log_advance_ready_for_write_lsn(log);}}?bool log_advance_ready_for_write_lsn(log_t &log) {const lsn_t write_lsn = log.write_lsn.load();const auto write_max_size = srv_log_write_max_size;?auto stop_condition = [&](lsn_t prev_lsn, lsn_t next_lsn) {return (next_lsn - write_lsn >= write_max_size);};const lsn_t previous_lsn = log_buffer_ready_for_write_lsn(log);?if (log.recent_written.advance_tail_until(stop_condition)) {const lsn_t previous_lsn = log_buffer_ready_for_write_lsn(log);return (true);} else {return (false);}}这里的关键在于函数 Link_buf::advance_tail_until ,即推进Link_buf::m_tail 。
bool Link_buf<Position>::next_position(Position position, Position &next) {const auto index = slot_index(position);auto &slot = m_links[index];const auto distance = slot.load();next = position + distance;return distance == 0;}?bool Link_buf<Position>::advance_tail_until(Stop_condition stop_condition) {auto position = m_tail.load();while (true) {Position next;bool stop = next_position(position, next);if (stop || stop_condition(position, next)) {break;}/* 回收slot */claim_position(position);position = next;}if (position > m_tail.load()) {m_tail.store(position);return true;} else {return false;}}这里的原理也比较简单,可以用下面的图来表示:

MySQL无锁化WAL系统那些事儿

文章插图
 
简单来说,就是从上次尾部位置(m_tail)开始,顺序遍历数组,如果该项不为0,则推进m_tail,否则意味着出现了空洞,就不能再往下推进了 。

【MySQL无锁化WAL系统那些事儿】


推荐阅读