mysql 8.4 中不能仅靠 set session transaction_read_only=on 实现高效只读,因其不自动禁用 binlog、仍分配事务 id、不绕过锁检查;必须使用 begin read only 语句级声明才能触发深度优化,如跳过 trx_id 分配、undo log 和事务系统表访问。

MySQL 8.4 中没有专为“只读事务”设计的独立开关参数,但可通过组合配置显著降低开销、避免不必要的日志刷盘和锁竞争。
为什么不能直接设 transaction_read_only=ON 就完事?
虽然 transaction_read_only 是会话级变量,设为 ON 后确实能禁止写操作并触发部分优化(如跳过 undo log 分配),但它不自动禁用 binlog 记录、不绕过事务 ID 分配、也不阻止隐式锁检查。尤其在高并发只读场景下,这些残留开销仍会拖慢吞吐。
- 默认情况下,即使事务内全是
SELECT,MySQL 仍会分配事务 ID、写入trx_id到聚集索引记录中(InnoDB 行格式要求) -
binlog_format = ROW时,只读事务虽不生成 event,但事务开始/结束仍可能触发 binlog cache 初始化 - 若未显式声明
BEGIN READ ONLY,而只是 SET SESSION transaction_read_only=ON,某些内部路径仍按可写事务处理
BEGIN READ ONLY 比 SET SESSION transaction_read_only=ON 更彻底
MySQL 8.4 对 BEGIN READ ONLY 有专门的执行路径优化:跳过事务系统表访问、避免分配 trx_id(使用只读事务 ID 池)、不写 undo log、且明确告知存储引擎“本次事务绝无写意图”。
- 必须用语句级方式:
BEGIN READ ONLY或START TRANSACTION READ ONLY,不能靠会话变量模拟 - 应用层需改写逻辑:把批量只读查询封装进
READ ONLY事务块,而非单条SELECT - 注意兼容性:
READ ONLY事务中禁止任何 DML/DAL/DDL,包括SELECT ... FOR UPDATE和SELECT ... LOCK IN SHARE MODE
配套关闭 binlog 和日志刷盘可进一步减负
若该 MySQL 实例确定不参与复制、且只读事务结果不要求强持久化(例如报表缓存、监控拉取),可临时关闭 binlog 写入与 redo 日志强制刷盘——但这属于有损优化,仅限特定场景。
- 对只读事务生效的最小干预:在会话中执行
SET SESSION sql_log_bin = OFF(需有SUPER权限) - 避免 redo 日志刷盘开销:
SET SESSION innodb_flush_log_at_trx_commit = 0,但注意这会让整个会话失去崩溃安全性 - 二者不可滥用:一旦混入写操作,
sql_log_bin = OFF会导致主从数据不一致;innodb_flush_log_at_trx_commit = 0在 crash 时丢失最近 1 秒事务
真正影响只读性能的其实是 buffer pool 和索引结构
参数调优只是辅助,真正的瓶颈往往在物理访问路径上。只读事务频繁扫描大表却没走索引,或缓冲池太小导致大量磁盘读,再怎么关日志也白搭。
- 确认
innodb_buffer_pool_size足够容纳热点只读数据集,命中率低于 95% 时优先扩容而非调参 - 对高频只读查询建覆盖索引,避免回表;用
EXPLAIN确认type是ref或更好,不是ALL - 避免在只读事务中执行
SELECT COUNT(*) FROM huge_table这类全表扫描,改用近似统计或物化视图
最容易被忽略的一点是:只读事务的“只读”必须由 SQL 层明确声明(BEGIN READ ONLY),而不是靠应用层约定或连接池自动设置。MySQL 不会主动推断你的意图,它只认语法。











