文章总结: SELECT…FORUPDATE通常锁行而非锁表,但实际行为受索引、数据库引擎、隔离级别等因素影响。MySQLInnoDB可能因GapLock扩大锁定范围,PostgreSQL则主要锁定返回行。建议为WHERE条件字段建立索引、保持事务简短、按固定顺序获取锁以提升并发性能。 综合评分: 88 文章分类: 技术标准,解决方案
面试官问我:select……for update会锁表还是锁行?
原创
库库 库库
数据库开发
2026年7月2日 16:59 福建
在小说阅读器读本章
去阅读
很多开发者第一次接触 SELECT ... FOR UPDATE,都是在解决并发更新问题时。
这个语句看起来很简单,却经常引出一个重要的问题:
SELECT ... FOR UPDATE会锁整个表,还是只锁查询到的行?
简短的答案是:通常锁的是行,而不是整个表。 但完整的答案取决于数据库引擎、索引、事务隔离级别,甚至取决于 SQL 语句本身。
什么是 SELECT … FOR UPDATE?
SELECT ... FOR UPDATE 是一种加锁读取(locking read)。与普通的 SELECT 只负责读取数据不同,它告诉数据库,这些查询出来的行很可能会在当前事务中稍后被更新。
当这些行被锁定后:
- 其他事务不能修改这些行。
- 其他针对相同行执行的
SELECT … FOR UPDATE必须等待。 - 这些锁会一直保持到事务提交(commit)或回滚(rollback)。
正如 PostgreSQL 官方文档所说,“FOR UPDATE会使SELECT语句返回的行按照更新操作的方式被锁定。"
一个常见的误解:”它会锁整个表”
很多开发者认为,SELECT ... FOR UPDATE 一定会锁整张表。
实际上,现代关系型数据库都会尽可能使用行级锁(row-level locking)。
例如:
BEGIN;
SELECT *
FROM orders
WHEREid = 100
FORUPDATE;
UPDATE orders
SETstatus = 'PAID'
WHEREid = 100;
COMMIT;
这里只有 id = 100 这一行会被锁住。
其他事务仍然可以:
- 读取其他行;
- 更新其他行;
- 插入无关的数据。
这种细粒度的加锁方式,也是 MySQL InnoDB 和 PostgreSQL 能够在高并发场景下保持良好扩展性的原因之一。
那为什么有人感觉”锁了整张表”?
如果 SELECT ... FOR UPDATE 正常情况下只锁行,那么为什么有些应用看起来像是把整个表都锁住了?
通常有下面几种情况。
1、没有合适的索引
假设执行下面这条 SQL:
SELECT *
FROM orders
WHERE customer_name = 'Alice'
FOR UPDATE;
如果 customer_name 没有索引,数据库就需要扫描整张表才能找到符合条件的数据。
虽然它的目标仍然是行级锁,但在执行过程中,可能会锁住表中大量的行,甚至锁住扫描到的所有行,从而大幅降低并发能力。在高并发场景下,这种阻塞效果往往与锁表十分接近。
数据库并不是主动进行了表锁,而是因为无法快速定位目标数据,只能锁住更多的行。
2、范围查询和 Gap Lock(MySQL InnoDB)
MySQL 的 InnoDB 存储引擎还引入了另外一个概念:Next-Key Lock(行锁 + Gap Lock)。
例如:
SELECT *
FROM orders
WHERE id BETWEEN 100 AND 200
FOR UPDATE;
除了锁住已经存在的数据行之外,InnoDB 还可能锁住这些索引记录之间的间隙(gap)。
这样做是为了防止其他事务向这个范围内插入新的数据,从而避免在默认的 REPEATABLE READ 隔离级别下出现幻读(phantom read)。
因此,开发者有时会误以为整个表都被锁住了,而实际上,被保护的是某一个索引范围。
3、没有匹配到任何数据
还有一种比较有意思的特殊情况,就是查询没有找到任何记录。
例如:
SELECT *
FROM orders
WHERE id = 9999
FOR UPDATE;
如果查询没有匹配到任何数据,情况则取决于事务隔离级别。
在 MySQL InnoDB 的默认 REPEATABLE READ(RR) 隔离级别下,即使没有找到 id = 9999 这条记录,数据库仍然可能获取 Gap Lock(间隙锁),锁住对应索引中的间隙,阻止其他事务插入 id = 9999 的记录,从而避免幻读(phantom read)。
而在 READ COMMITTED(RC) 隔离级别下,对于这种未命中记录的等值查询,一般不会获取 Gap Lock,因此不会因为查不到数据而阻塞后续插入。
4、不同的存储引擎
存储引擎也会影响锁的行为。
-
InnoDB
支持行级锁。
-
MyISAM
使用表级锁。
因此,同样一条 SQL,在不同的存储引擎下,表现可能完全不同。
MariaDB 官方文档也提到,像 MyISAM 这样的非事务型存储引擎使用的是表级锁。
PostgreSQL 与 MySQL 的区别
虽然两者都支持 SELECT ... FOR UPDATE,但实现方式并不完全一样。
PostgreSQL
PostgreSQL 只会锁住查询返回的数据行。
它内部也会获取一个轻量级的表锁,用于协调元数据,但这个锁不会阻止其他事务正常读取或修改其他行。真正产生阻塞作用的是行级锁。
此外,PostgreSQL 的 FOR UPDATE 不会产生 Gap Lock(间隙锁),因此不存在 MySQL InnoDB 中因 Gap Lock 而阻止其他事务插入数据的情况。
MySQL InnoDB
InnoDB 同样优先使用行级锁。
但是,根据下面几个因素:
- 事务隔离级别;
- 是否使用索引;
- 查询条件;
它还可能产生 Gap Lock 或 Next-Key Lock,因此实际锁定的范围可能比开发者预期的更大。
最佳实践
为了让 SELECT ... FOR UPDATE 获得最好的性能,可以遵循下面几点:
-
WHERE 条件中的字段尽量建立索引。
-
事务尽量保持简短,让锁尽快释放。
-
只锁真正需要更新的数据。
-
避免在事务中执行全表扫描。
-
涉及多行数据时,按照固定顺序获取锁,以降低死锁发生的概率。
PostgreSQL 社区中也经常建议采用一致的加锁顺序。
总结
SELECT ... FOR UPDATE 本质上是一种行锁机制(row-locking mechanism),而不是表锁命令。
不过,实际的加锁行为会受到多个因素影响:
- 是否存在索引;
- 数据库引擎;
- 事务隔离级别;
- 查询条件;
- 存储引擎的加锁策略。
在 PostgreSQL 中,SELECT ... FOR UPDATE 主要锁定查询返回的那些行;而在 MySQL InnoDB 中,同样采用行级锁,但在某些情况下,Gap Lock 和 Next-Key Lock 会扩大实际的加锁范围。
理解这些细节,有助于解释为什么一条在开发环境看起来毫无问题的 SQL,在生产环境的高并发场景下,却可能表现出完全不同的行为。
与其问:
“
SELECT ... FOR UPDATE到底锁表还是锁行?”
经验丰富的数据库工程师更倾向于问:
“这条 SQL 到底会锁住哪些行,以及哪些索引范围?”
这两者之间的区别,往往正是构建高并发数据库应用的关键。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:数据库开发 库库 库库《面试官问我:select……for update会锁表还是锁行?》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。












评论