START TRANSACTION READ ONLY 不一定提速,仅在 MySQL 5.7+ 且全程无隐性写、锁、变量赋值时生效;需通过 INNODB_STATUS 或 INNODB_TRX 确认 TRX_IS_READ_ONLY=1 且 TRX_ROWS_LOCKED=0。

加了 START TRANSACTION READ ONLY 就能提速?不一定。它只在 MySQL 5.7+ 真正生效,且必须全程“干净”——没隐性写、没锁、没变量赋值,否则优化直接失效。
怎么确认你的只读事务真被识别了
MySQL 不会告诉你“已启用只读优化”,得自己查:
- 执行
SHOW ENGINE INNODB STATUS\G,搜TRANSACTIONS部分,看当前事务的TRX_IS_READ_ONLY是否为1(不是0或缺失) - 查
INFORMATION_SCHEMA.INNODB_TRX,确认TRX_IS_READ_ONLY = 1且TRX_ROWS_LOCKED = 0(说明没走写路径) - 若
TRX_STATE是RUNNING但TRX_STARTED很早、TRX_IS_READ_ONLY = 0,说明你声明了但被降级为普通事务——大概率是版本太低或混入了隐性写
哪些操作会让 START TRANSACTION READ ONLY 失效
不是不写 UPDATE 就安全。这些行为会让 InnoDB 立刻标记事务为“可写”,跳过所有优化:
-
SELECT ... FOR UPDATE或SELECT ... LOCK IN SHARE MODE—— 显式加锁即视为写意图 - 调用
UUID()、NOW()(尤其 binlog_format=STATEMENT 时)、USER()等可能触发内部写或日志记录的函数 -
SET @var = 1或SET SESSION sort_buffer_size = 2097152—— 会话变量赋值在部分版本中触发“潜在可写”标记 -
CREATE TEMPORARY TABLE—— 即使只读查询也强制走写路径 - 子查询里碰了写表,比如
SELECT * FROM t WHERE id > (SELECT MAX(id) FROM log_table),而log_table正在被其他事务大量写入
START TRANSACTION READ ONLY 和 AUTOCOMMIT = 1 的区别
两者都能让单条 SELECT 走轻量路径,但适用场景完全不同:
-
AUTOCOMMIT = 1:每个SELECT是独立单语句事务,不分配trx_id、不进活跃链表、无一致性快照复用能力——适合监控、报表等“查完就扔”的场景 -
START TRANSACTION READ ONLY:显式开启多语句事务,可跨多个SELECT复用同一read_view(比如先SELECT COUNT(*)再分页查数据),但必须严格守约,一旦违规立刻报错ERROR 1792 (25006): Cannot execute statement in a READ ONLY transaction - 注意:
SET TRANSACTION READ ONLY必须在START TRANSACTION之前执行;如果已经BEGIN了再设,对当前事务无效
最容易被忽略的性能瓶颈点
就算 TRX_IS_READ_ONLY = 1 且没报错,也不代表快——优化只省掉事务 ID 分配、undo log 初始化、活跃事务链表注册这几步开销。如果查询本身走全表扫描、没索引、或二级索引上锁太多(比如 SELECT ... WHERE status = ? 但 status 列没索引),CPU 和 IO 耗在磁盘读取上,只读标记根本救不了。
真正要盯的是:是否真需要跨语句一致性快照?如果不是,关掉 AUTOCOMMIT = 0 改用单语句模式更轻量;如果需要,就务必确保整段事务从头到尾“零污染”——连 SET @x := 1 这种看似无害的操作都得砍掉。



















