只读事务仍需开启事务以保证可重复读隔离级别下的快照一致性,InnoDB会分配事务ID、创建read view;显式声明START TRANSACTION READ ONLY可跳过ID分配、不注册活跃链表,显著降低开销。

只读事务为什么还要开事务?
MySQL 的 BEGIN 或 START TRANSACTION 即使后面全是 SELECT,InnoDB 仍会分配事务 ID、创建事务对象、维护一致性视图(read view)。这不是“没用的开销”,而是为了保证可重复读(RR)隔离级别下多次 SELECT 看到相同快照——哪怕你只查一次,InnoDB 也得准备好这个能力。
常见错误现象:SHOW ENGINE INNODB STATUS 里看到大量 TRX_STATE: RUNNING 的只读事务长期不释放,INFORMATION_SCHEMA.INNODB_TRX 中 TRX_ISOLATION_LEVEL 是 REPEATABLE READ 但 TRX_ROWS_LOCKED 为 0,说明它没锁行,但仍在占用内存和 undo 段元数据。
- 显式开启的只读事务(
BEGIN; SELECT ...;)一定会走完整事务生命周期 - 自动提交关闭(
autocommit=0)后任意SELECT都会隐式启动事务,容易被忽略 - 即使加了
SELECT ... FOR UPDATE或LOCK IN SHARE MODE,事务性质就变成“读写”,优化逻辑完全不同
MySQL 5.6+ 的 innodb_read_only 和只读事务有关系吗?
没有直接关系。innodb_read_only=ON 是实例级开关,禁止所有写操作(包括 DDL),它会让 InnoDB 跳过事务日志写入、禁用 insert buffer 合并等,但对单个只读事务的内部开销(如 read view 分配)几乎无影响。它优化的是“整个实例只读”的场景,不是“某个事务只读”。
真正影响只读事务资源消耗的是:是否显式声明 START TRANSACTION READ ONLY(MySQL 5.6+ 支持)。
-
START TRANSACTION READ ONLY会让 InnoDB 标记该事务为“严格只读”,跳过事务 ID 分配(复用全局只读事务 ID)、不注册到活跃事务链表、不参与 purge 线程可见性判断 - 必须在事务开始时声明,
SET TRANSACTION READ ONLY只影响后续事务,不改变当前已开启的事务类型 - 如果事务中混入任何写语句(哪怕只是
SELECT INTO OUTFILE或调用含写逻辑的函数),MySQL 会立即报错ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION
如何识别和避免无效的只读事务?
最典型的浪费是应用层“习惯性开事务 + 单条 SELECT”,比如 ORM 自动生成 BEGIN; SELECT ...; COMMIT;。这类事务既没并发一致性需求,又没跨语句关联,纯属冗余。
检查方法:
- 查
INFORMATION_SCHEMA.INNODB_TRX,重点关注TRX_STARTED时间早、TRX_ROWS_LOCKED = 0、TRX_IS_READ_ONLY = 0的长事务 - 开启慢查询日志 +
long_query_time = 0,过滤出执行时间极短但带BEGIN/COMMIT的 SELECT - 用 Performance Schema 的
events_transactions_current表,按EVENT_NAME和STATE追踪事务状态流转
优化建议:
- 确认业务是否真需要 RR 隔离——很多只读接口用
READ COMMITTED就够,甚至可以关掉事务直接查(autocommit=1) - 把
START TRANSACTION READ ONLY作为只读查询的默认模板,尤其在连接池场景下能显著降低 purge 压力 - 避免在存储过程中无条件
START TRANSACTION,应根据参数或上下文分支决定是否开启
只读事务和 MVCC 快照的关系到底是什么?
关键点:只读事务的资源消耗主要来自 MVCC 快照(read view)的创建与维护,而不是“读”本身。InnoDB 在事务第一次执行 SELECT 时才构建 read view,但事务对象和内存结构在 BEGIN 时就已分配。
性能影响集中在三处:
- 全局 read view 链表增长:每个活跃只读事务都向
trx_sys->view_list插入节点,purge 线程遍历时需遍历全部 - undo log 不可清理:只要存在比某 undo 记录更老的 read view,对应旧版本数据就不能被 purge,导致
INFORMATION_SCHEMA.INNODB_METRICS中dml_undo_log_discarded下降 - 内存碎片:短生命周期只读事务频繁创建/销毁,可能加剧
trx_sys->mysql_trx_list的链表操作开销(尤其高并发小事务场景)
所以,哪怕一个只读事务只执行一条 SELECT 并立刻 COMMIT,只要它没用 READ ONLY 声明,就仍会参与上述机制——这点最容易被忽略。


















