MySQL 中 SELECT
语句是否加锁?详细解析
一、通常的 SELECT
是不是加锁?
默认情况下(普通 SELECT
查询):
-
InnoDB 引擎下,普通的
SELECT
查询不会加行锁或者表锁。 -
只会用到 一致性读(Consistent Read),通过 MVCC(多版本并发控制) 实现,无需加锁即可读取当前事务可见的数据快照。
✅ 普通 SELECT
是非阻塞的,且不会加锁。
二、哪些 SELECT
语句确实会加锁?
并不是所有 SELECT
都不加锁,以下几种情况,SELECT
会显式加锁:
1. SELECT ... LOCK IN SHARE MODE
-
加共享锁(S锁),锁定选中的行,防止其他事务对这些行进行修改或删除。
-
其他事务可以继续读,但不能改。
示例:
SELECT * FROM user WHERE id = 10 LOCK IN SHARE MODE;
特点:
-
当前事务可以继续读这行;
-
其他事务可以读;
-
但其他事务不能更新或删除这些行,否则阻塞。
常用于:需要读取数据且后续基于这些数据进行一些决策,确保读到的数据在后续处理前不会被别人修改。
2. SELECT ... FOR UPDATE
-
加排他锁(X锁),锁定选中的行。
-
当前事务可以读,也可以改;
-
其他事务不能读(如果是
RC
隔离级别还能读但不能改),也不能改、不能删。
示例:
SELECT * FROM user WHERE id = 10 FOR UPDATE;
特点:
-
只有当前事务能读写这些行;
-
其他事务读(隔离级别有关)或者写都会阻塞。
常用于:
-
乐观锁/悲观锁场景。
-
实现扣库存、扣余额等强一致性更新操作。
3. 可重复读隔离级别下,防止幻读(隐式加锁)
在 REPEATABLE READ
隔离级别下,即使普通 SELECT
,为了防止幻读,也可能触发内部加锁机制(特别是间隙锁、临键锁)。
比如在某些情况下,针对索引范围的扫描,会加上间隙锁,防止其他事务插入新记录,造成幻读。
三、锁类型汇总(带示意)
查询方式 | 是否加锁 | 加锁类型 |
---|---|---|
普通 SELECT (无加锁语法) | 否 | 只使用MVCC,生成一致性快照 |
SELECT ... LOCK IN SHARE MODE | 是 | 共享锁(S锁) |
SELECT ... FOR UPDATE | 是 | 排他锁(X锁) |
隔离级别为 REPEATABLE READ 时的范围查询 | 可能 | 隐式 Next-Key Lock(行锁+间隙锁) |
四、示例总结
1. 普通查询(不加锁)
SELECT * FROM orders WHERE id = 123;
-
不加锁;
-
快照读,读取事务启动时可见的数据版本。
2. 加共享锁
SELECT * FROM orders WHERE id = 123 LOCK IN SHARE MODE;
-
加 S锁,防止被别人修改;
-
适合读后校验,但不修改的场景。
3. 加排他锁
SELECT * FROM orders WHERE id = 123 FOR UPDATE;
-
加 X锁,其他事务不能改也不能删;
-
适合读后即将修改的场景,比如扣减库存。
五、注意事项
-
FOR UPDATE
和LOCK IN SHARE MODE
必须在事务中使用(START TRANSACTION
),否则加锁就没有意义,MySQL自动提交后锁立即释放。 -
如果没有显式加锁,普通
SELECT
默认采用非锁定一致性读,走 MVCC 快照机制,不阻塞,不加锁,性能最好。 -
加锁的
SELECT
查询,涉及的行需要有索引,否则可能导致范围过大,性能变差甚至锁表。
最终一句话总结
✅ 普通 SELECT 不加锁,靠 MVCC;加了 LOCK IN SHARE MODE
或 FOR UPDATE
的 SELECT,才加共享锁或排他锁。