只读事务能绕过MDL锁,是因为显式声明READ ONLY后,MySQL跳过对多数表的MDL升级逻辑,仅在真正涉及结构变更时加锁;而普通SELECT默认按“可能写”处理,会加MDL_SHARED_READ锁阻塞DDL。

为什么只读事务能绕过元数据锁(MDL)
MySQL 的元数据锁(MDL)在普通 SELECT 语句执行时也会被持有——只要该会话尚未显式声明为只读,InnoDB 就默认按“可能写”处理,从而在表级别加 MDL_SHARED_READ 锁。这会导致 DDL 操作(如 ALTER TABLE)被阻塞,尤其在高并发只读场景下成为隐形瓶颈。
而显式开启只读事务后(START TRANSACTION READ ONLY 或 SET TRANSACTION READ ONLY),MySQL 会跳过对大多数表的 MDL 升级逻辑,仅在真正访问表结构变更的极少数路径中才加锁。实测表明:在主从延迟敏感或频繁 DDL 的系统中,这一设置可使 ALTER TABLE 等操作平均提前 60%–90% 完成。
如何正确启用只读事务并避免常见失效
只读事务不是“加个关键字就生效”,它依赖两个条件同时满足:
-
transaction_read_only = ON(会话或全局) + 显式声明START TRANSACTION READ ONLY - 事务内**不能有任何写操作**,包括:
INSERT、UPDATE、DELETE、SELECT ... FOR UPDATE、LOCK IN SHARE MODE,甚至CREATE TEMPORARY TABLE也会导致只读标记被自动清除
容易踩的坑:
- ORM 框架(如 MyBatis、Hibernate)可能在事务开启后悄悄执行
SELECT @@version或SELECT DATABASE()——这些看似只读的语句若发生在READ ONLY事务开启前,会触发隐式 MDL;建议在事务开始前统一用SET SESSION transaction_read_only = ON预置 - 使用
SET autocommit = 0后再SET TRANSACTION READ ONLY是无效的,必须搭配START TRANSACTION或BEGIN
只读事务对索引开销的影响有限,别误信“自动跳过索引”
只读事务本身**不会减少索引查找或回表开销**。它不改变查询执行计划,也不跳过 B+ 树遍历。所谓“降低索引开销”,实际是指:在只读事务中,你可以更安全地启用 innodb_optimize_fulltext_only(全文索引优化)或配合 read_buffer_size 调优,但核心仍取决于索引设计是否合理。
真正影响索引效率的是以下几点:
- WHERE 条件是否命中索引(避免
WHERE DATE(create_time) = '2024-01-01'这类函数操作) - 是否用了覆盖索引(
SELECT id, name FROM user WHERE status=1,且status、id、name都在同一个联合索引里) - 扫描行数是否被
EXPLAIN中的rows值准确反映(注意filtered字段,低值说明索引选择性差)
生产环境推荐的最小安全配置组合
只读事务的价值,在于它把“语义确定性”交还给数据库。但光靠它不够,需配套落地:
- 应用层:对明确无写的接口(如商品详情页、用户资料页),统一用
BEGIN READ ONLY开启事务,结束后COMMIT(不是ROLLBACK,否则 MySQL 会重置只读状态) - 连接池:在初始化连接时执行
SET SESSION transaction_read_only = ON,避免每次请求都重复设置 - 监控项:定期检查
performance_schema.threads中TRANSACTION_ISOLATION和TRANSACTION_READ_ONLY字段,确认只读标识真实生效 - 警惕例外:含子查询的
SELECT若子查询含写操作(哪怕未执行),整个事务仍会被标记为“非只读”——这类边界情况只能靠EXPLAIN FORMAT=TREE+ 日志交叉验证
最常被忽略的一点:只读事务无法规避因缺失索引导致的全表扫描,它只管锁,不管查。索引没建对,再“只读”也救不了慢查询。


















