mysql默认select在rr和rc隔离级别下为快照读,不加锁;但使用for update、lock in share mode、索引失效或隐式锁升级时会加锁,需关注隔离级别、索引优化与事务设计。

MySQL 默认的 SELECT 就是快照读,但得看隔离级别
MySQL 的 SELECT 在可重复读(REPEATABLE READ)和读已提交(READ COMMITTED)下默认走快照读,不加锁、不阻塞写操作。但这不是绝对的——如果用了 SELECT ... FOR UPDATE 或 SELECT ... LOCK IN SHARE MODE,哪怕在 RR 级别也会加行锁。
常见错误现象:明明没写 FOR UPDATE,却观察到查询卡住或被其他事务阻塞。大概率是应用层 ORM 自动生成了锁语句,或者执行计划触发了间隙锁(比如 WHERE name > 'a' 且没有索引时)。
- 确认当前会话隔离级别:
SELECT @@transaction_isolation; - 快照读只对普通
SELECT生效;带锁子句、UPDATE/DELETE的 WHERE 条件扫描一定加锁 - RR 级别下,快照基于事务启动时的“一致性视图”(Read View),不是实时的;RC 级别下,每次
SELECT都新建 Read View
想确保非阻塞,得避开隐式锁升级和索引失效
快照读虽不加锁,但若 SQL 走不到索引,InnoDB 可能退化为全表扫描 + 行锁(尤其在 RC 下),甚至触发表级锁提示(如 LOCK TABLES 误用)。更隐蔽的是:某些函数或表达式会让索引失效,导致本该走快照读的语句实际要扫描大量行,间接拉高锁冲突概率。
- 避免在
WHERE中对索引字段用函数:WHERE DATE(create_time) = '2024-01-01'→ 改成WHERE create_time >= '2024-01-01' AND create_time - 检查执行计划:
EXPLAIN SELECT ...,确认type是ref/range,不是ALL - 字符串比较注意隐式类型转换:
WHERE user_id = '123'(user_id是INT)可能使索引失效
显式控制快照版本:用 START TRANSACTION WITH CONSISTENT SNAPSHOT
这个语法强制开启一个带一致性快照的新事务,适合需要多次 SELECT 之间数据逻辑一致的场景(比如报表汇总)。但它不会阻止后续写入,只是固定了读视图起点。
- 仅对
REPEATABLE READ有效;在READ COMMITTED下会被忽略 - 不能跨连接复用快照;每个连接必须自己执行该语句
- 事务开启后第一次
SELECT才建立快照,之前的操作(如SET变量)不影响快照内容
START TRANSACTION WITH CONSISTENT SNAPSHOT; SELECT COUNT(*) FROM orders WHERE status = 'paid'; SELECT SUM(amount) FROM orders WHERE status = 'paid'; -- 两次查询看到相同数据集
真正无锁 ≠ 绝对零开销,MVCC 本身有成本
快照读依赖 MVCC(多版本并发控制),每行记录都存着隐藏字段 DB_TRX_ID 和 DB_ROLL_PTR,回滚段(undo log)也要持续维护。长事务会拖慢 purge 线程,导致 undo log 膨胀、历史版本堆积,最终影响查询性能甚至磁盘空间。
- 监控长事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60; - 避免在事务里做耗时操作(如 HTTP 请求、文件读写)
- 业务上允许的话,把大查询拆成小批次,用
LIMIT+WHERE id > ?游标分页,减少单次快照覆盖范围
快照读是否“无锁”,最终取决于你有没有触发行锁条件、索引是否生效、事务是否过长——这些比记住“默认快照读”重要得多。










