mysql中使用SELECT FOR UPDATE进行事务锁定

来源:这里教程网 时间:2026-02-28 20:42:41 作者:

SELECT FOR UPDATE 只在事务中生效

单独执行

SELECT FOR UPDATE
不会加锁,必须显式开启事务(
BEGIN
START TRANSACTION
),且不能在自动提交模式下运行。否则语句看似执行成功,实则退化为普通查询,锁根本不会产生。

检查当前会话是否自动提交:
SELECT @@autocommit;
,返回
1
表示开启,需先执行
SET autocommit = 0;
推荐写法:始终用
BEGIN; SELECT ... FOR UPDATE; [UPDATE/DELETE]; COMMIT;
显式控制边界
如果用 ORM(如 SQLAlchemy、MyBatis),确认其事务配置未覆盖底层隔离级别或提前 commit

锁定范围取决于 WHERE 条件和索引

SELECT FOR UPDATE
加的是行级锁,但“哪些行被锁”完全由查询条件是否命中索引决定。没走索引时会升级为表锁(InnoDB 中实际是所有索引记录的 next-key 锁,效果接近全表扫描锁)。

主键或唯一索引查询(如
WHERE id = 100
)→ 精确锁定单行
普通索引 + 范围查询(如
WHERE status = 'pending'
)→ 锁定所有匹配索引项及其间隙(next-key lock)
无索引字段查询(如
WHERE remark LIKE '%test%'
)→ 全表扫描,锁住所有聚簇索引记录,高并发下极易阻塞
验证是否走索引:
EXPLAIN SELECT ... FOR UPDATE
,重点看
type
(应为
const
/
ref
/
range
)和
key
字段

避免死锁的关键操作习惯

死锁不是异常,而是 InnoDB 的正常检测行为。多数源于多个事务以不同顺序访问相同资源。只要加锁顺序一致,就能大幅降低概率。

所有业务逻辑中,对多行加锁时,强制按主键升序排序后再查:
SELECT ... FOR UPDATE ORDER BY id ASC
不要在事务内混合使用
SELECT FOR UPDATE
SELECT LOCK IN SHARE MODE
避免在事务中执行耗时操作(如 HTTP 请求、文件读写),锁持有时间越长,冲突窗口越大 监控死锁日志:
SHOW ENGINE INNODB STATUS\G
,重点关注
LATEST DETECTED DEADLOCK
段落

UPDATE 语句本身也隐式加锁,别重复 FOR UPDATE

如果目标只是“查出来再更新”,直接用

UPDATE ... WHERE
更高效。它内部已对匹配行加 X 锁,无需先
SELECT FOR UPDATE
UPDATE
—— 多一次查询不仅浪费 I/O,还可能因中间状态变化导致业务逻辑错乱。

UPDATE orders SET status = 'shipped' WHERE id = 123 AND status = 'paid';

这种写法原子性强,还能防止并发修改覆盖(通过

status = 'paid'
做乐观校验)。只有需要基于查询结果做复杂判断(比如库存预占需先读取当前余量再决定是否扣减)时,才真正需要
SELECT FOR UPDATE

最容易被忽略的是:锁只在事务提交或回滚后释放,哪怕你只执行了

SELECT FOR UPDATE
并没后续操作,连接不关闭、事务不结束,锁就一直挂着——这会悄无声息拖垮整个库的并发能力。

相关推荐