面试官问我:select……forupdate会锁表还是锁行?

admin 2026-07-05 06:09:57 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: SELECT…FORUPDATE通常锁行而非锁表,但实际行为受索引、数据库引擎、隔离级别等因素影响。MySQLInnoDB可能因GapLock扩大锁定范围,PostgreSQL则主要锁定返回行。建议为WHERE条件字段建立索引、保持事务简短、按固定顺序获取锁以提升并发性能。 综合评分: 88 文章分类: 技术标准,解决方案


cover_image

面试官问我: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会锁表还是锁行?》

评论:0   参与:  0