【华为云MySQL技术专栏】Binlog压缩:节省存储,优化网络,提升性能
1.背景及概述
2.原理浅析
class Transaction_payload_event : public Binary_log_event { protected: //event所包含的数据信息 const char *m_payload{nullptr}; //压缩后的数据信息大小 uint64_t m_payload_size{0}; //压缩方式,定义见后,当前仅包含代表非压缩的NONE和ZSTD方式 transaction::compression::type m_compression_type{ transaction::compression::type::NONE}; //压缩前的数据信息大小 uint64_t m_uncompressed_size{0}; ...... } //定义压缩方式 enum type { /* ZSTD compression. */ ZSTD = 0, /* No compression. */ NONE = 255, };
调用堆栈: #0 in binlog_cache_data::finalize #1 in MYSQL_BIN_LOG::commit #2 in ha_commit_trans #3 in trans_commit #4 in mysql_execute_command #5 in dispatch_sql_command #6 in dispatch_command #7 in do_command int binlog_cache_data::finalize(THD *thd, Log_event *end_event) { …… if (int error = flush_pending_event(thd)) return error; if (int error = write_event(end_event)) return error; if (int error = this->compress(thd)) return error; …… }
bool binlog_cache_data::compress(THD *thd) { ... // binlog_transaction_compression参数值判断 if (thd->variables.binlog_trx_compression == false) goto end; ... //校验是否存在非压缩场景 if (has_incident()) goto end; if (thd->get_transaction()->has_modified_non_trans_table( Transaction_ctx::STMT) || thd->get_transaction()->has_modified_non_trans_table( Transaction_ctx::SESSION)) goto end; if (may_have_sbr_stmts()) goto end; ... //获取压缩器,并检查是否需要申请更大内存空间 if ((compressor = cctx.get_compressor(thd)) == nullptr) goto end; ... Transaction_payload_log_event tple{thd}; ctype = compressor->compression_type_code(); compressor->open(); stream.set_compressor(compressor); //将cache中的数据拷贝压缩 if ((error = m_cache.copy_to(&stream))) goto compression_end; compressor->close(); //基于压缩后的数据生成Transaction_payload_log_event的内容 std::tie(buffer, size, std::ignore) = compressor->get_buffer(); tple.set_payload((const char *)buffer); //设置压缩后数据的大小 tple.set_payload_size(size); //设置压缩方式 tple.set_compression_type(ctype); //设置压缩前的数据大小 tple.set_uncompressed_size(uncompressed_size); //将event写入cache中 error = write_event(&tple); //释放新申请buffer空间,压缩器中重置为旧buffer空间 if (old_buffer) { std::tie(buffer, std::ignore, std::ignore) = compressor->get_buffer(); compressor->set_buffer(old_buffer, old_capacity); free(buffer); } }
int Transaction_payload_log_event::do_apply_event(Relay_log_info const *rli) { ... //依次解压缩并处理事务中包含的各个log event for (auto ptr : it) { ... if ((res = apply_payload_event(rli, (const uchar *)ptr))) break; ... } ... } bool Transaction_payload_log_event::apply_payload_event( Relay_log_info const *rli, const uchar *event_buf) { ... if (binlog_event_deserialize(ptr, event_len, &fdle, true, &ev)) { res = true; goto end; } ... if (is_mts_worker(thd)) { ... //并行方式,由worker线程回放 res = ev->do_apply_event_worker(worker); } else { ... //非并行方式,由applier线程回放 res = ev->apply_event(coord); } ... }
void Transaction_payload_log_event::print(FILE *, PRINT_EVENT_INFO *info) const { ... //打印压缩事务的起始格式 if (!info->short_form) { std::ostringstream oss; oss << "\tTransaction_Payload\t" << to_string() << std::endl; oss << "# Start of compressed events!" << std::endl; print_header(head, info, false); my_b_printf(head, "%s", oss.str().c_str()); } //遍历并依次打印事务中各个log event binary_log::transaction::compression::Iterable_buffer it( m_payload, m_payload_size, m_uncompressed_size, m_compression_type); for (auto ptr : it) { ... if (binlog_event_deserialize((const unsigned char *)buffer, event_len, &glob_description_event, true, &ev)) ... process_event(info, ev, header()->log_pos, "", true); } ... //打印压缩事务的结束格式 if (!info->short_form) my_b_printf(head, "# End of compressed events!\n"); ... }
mysql> show global variables like '%binlog_transaction_compression%'; +-------------------------------------------+-------+ | Variable_name | Value | +-------------------------------------------+-------+ | binlog_transaction_compression | OFF | | binlog_transaction_compression_level_zstd | 3 | +-------------------------------------------+-------+ 2 rows in set (0.02 sec) //开启binlog压缩 mysql> set global binlog_transaction_compression = on; Query OK, 0 rows affected (0.00 sec) //根据需要,调整zstd压缩等级 mysql> set global binlog_transaction_compression_level_zstd = 7; Query OK, 0 rows affected (0.00 sec)
binlog_transaction_compression_level_zstd = 3 未开启压缩binlog文件大小,1.84 GB 开启压缩后binlog文件大小,0.89 GB
1.背景及概述
Binlog(二进制日志)记录了数据的更改操作,具有极为重要的作用,可用于数据恢复、数据复制和审计。然而,当用户在使用MySQL时,如果写入业务量过大,可能会导致磁盘空间和网络带宽的过度使用。 为了应对这一情况,MySQL 8.0.20版本推出了基于zstd压缩算法的binlog压缩功能。 本文将从源码角度对binlog压缩功能进行浅析,帮助读者了解其使用方式。同时,通过测试结果展示该功能的效果和性能影响,体现binlog压缩功能为用户带来了较好的应用价值。
2.原理浅析
MySQL 8.0.20版本增加的binlog日志事务压缩功能,通过使用zstd算法对事务信息进行压缩,然后再写入binlog文件。压缩事务由Transaction_payload_log_event对象处理,该对象继承自Transaction_payload_event和Log_event。压缩后的事务内容保存在Transaction_payload_event中,核心成员如下所示。
压缩binlog的时机为写入缓存并提交事务时,通过调用this->compress(thd)来完成压缩动作,相关调用堆栈及函数如下所示。
binlog压缩处理函数即为binlog_cache_data::compress,其流程图如下。

图1 binlog压缩处理流程图
该函数首先会判断binlog_transaction_compression参数对应的变量,如果为true,则继续进行压缩处理。之后,函数会判断是否存在Incident events、非事务性更改等情况,若存在,则不能进行压缩。通过这些场景校验后,函数获取压缩器,并根据需要申请更大的新内存空间。随后,基于cache(缓存)中压缩的内容生成Transaction_payload_log_event,并设置压缩前后的相关元信息,函数的主要流程如下所示。
备机接收到Transaction_payload_log_event后,会将其写入RelayLog中,并由worker或者applier线程负责解析回放binlog中的各个log event(日志事件),核心处理逻辑如下所示。
此外,利用mysqlbinlog工具解析带有压缩事务的binlog文件时,相关调用路径也类似回放时,需要依次遍历解析并打印压缩事务中的各个log event。
3.使用方式 & 效果性能验证
binlog压缩相关控制变量具体描述见表1。

表1 binlog压缩相关参数说明
同时,可通过如下所示的show variables命令查看当前参数值,通过set命令进行参数值的更新。
压缩比验证:
创建8U32G的实例,使用sysbench导入250张表,每张表包含25000行数据。开启32个压测线程,在混合读写模型(oltp_read_write)下多次开启压缩/非开启压缩,进行长达60s的压测,对比压测结束后binlog文件大小。
结果显示,压缩比(压缩前/压缩后)约为2,binlog压缩功能表现出较明显的压缩效果。
性能验证:
创建8U32G的实例,使用sysbench导入250张表,每张表包含25000行数据。开启32个压测线程,设置压缩级别binlog_transaction_compression_level_zstd为3,在混合读写模型(oltp_read_write)下多次开启压缩/非开启压缩,完成压测后取平均值数据。 结果显示,基于上述场景,压缩后相对于压缩前性能劣化约3%。
总结
基于zstd压缩算法的binlog压缩功能,以较小的性能劣化,实现了存储开销减半,并节省了网络带宽。该功能在绝大多数实际使用场景中表现出较好的应用效果。
参考资料
[1] https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-20.html
[2] MySQL binlog 压缩功能对性能的影响,https://blog.csdn.net/weixin_39629075/article/details/113557347
[3] 在降本增效背景下谈MySQL压缩功能,https://zhuanlan.zhihu.com/p/629972777