单条带条件的UPDATE+影响行数校验是最轻量可靠的防覆盖方案;它将业务校验塞入WHERE子句,避免静默覆盖,且无需加锁、减少阻塞;SELECT FOR UPDATE仅在必须读旧值再计算时才用,但易引发死锁或长等待。

单条带条件的 UPDATE + 影响行数校验,比加锁更轻量、更少阻塞;SELECT FOR UPDATE 反而容易引发死锁或长等待,只在必须读旧值再计算时才考虑。
UPDATE WHERE 条件必须包含业务约束
执行 UPDATE accounts SET balance = 150 WHERE id = 123 这种无判断赋值,会无视当前值直接覆盖。两个并发事务都这么写,后提交者必然覆盖前者——这不是“阻塞”,是静默覆盖。
真正防覆盖+防阻塞的关键,是把校验逻辑塞进 WHERE 子句:
-
UPDATE accounts SET balance = balance + 50 WHERE id = 123 AND balance >= 50:余额不足则整条语句不生效,mysql_affected_rows()或@@ROWCOUNT返回 0 - 必须确保
id或balance字段有索引,否则可能触发全表扫描+表级锁,反而加剧阻塞 - 该写法在 MySQL(READ COMMITTED / REPEATABLE READ)、SQL Server、PostgreSQL 下均安全,无需额外事务控制
SELECT FOR UPDATE 不是阻塞“解药”,而是新风险源
很多人以为加了 FOR UPDATE 就能避免阻塞,结果线上频繁卡顿甚至死锁。根本问题在于:它延长了锁持有时间,且极易因使用不当锁错范围。
- 在事务开头执行
SELECT * FROM orders WHERE user_id = 123 FOR UPDATE,接着调外部 HTTP 接口耗时 2 秒,再UPDATE——这 2 秒内所有同用户请求全被阻塞 -
WHERE user_id = ?没走索引 → MySQL 锁整张表,吞吐断崖下跌 - Oracle 默认 autocommit=ON,
SELECT FOR UPDATE执行完立刻释放锁,后续UPDATE是全新事务,完全不受保护 - SQL Server 写成
SELECT ... WITH (UPDLOCK)却漏掉BEGIN TRANSACTION→ 锁语句一结束就释放,形同虚设
批量 UPDATE 或高并发场景下,锁顺序比锁本身更关键
死锁不是并发高导致的,而是多个事务对同一组行以不同顺序加锁。比如事务 A 先锁 id=1 再锁 id=5,事务 B 反过来,立刻循环等待。
-
ORDER BY不等于锁顺序可控——必须用EXPLAIN FORMAT=TRADITIONAL验证是否真走索引、Extra不含Using filesort - 复合索引注意最左前缀:有
(status, id)索引时,WHERE status = 'pending' ORDER BY id才有效;仅WHERE id > 100则基本无效 - 避免对非唯一字段(如
name)做SELECT FOR UPDATE,尤其值重复率高时,间隙锁冲突概率陡增 - 查不到记录也会死锁:
SELECT * FROM orders WHERE order_no = 'ABC' FOR UPDATE在 RR 隔离级下会对间隙加锁,多个并发请求争抢同一间隙,叠加 INSERT 意向锁即构成死锁链
超时设置必须分层落地,不能只靠数据库默认值
不设超时,一个长事务就能让后续所有相关 UPDATE “冻结”。但数据库级超时只能管“等锁”,不能覆盖网络中断、客户端崩溃等场景。
- MySQL:在
START TRANSACTION后、UPDATE前执行SET SESSION innodb_lock_wait_timeout = 5,超时报Lock wait timeout exceeded - PostgreSQL:必须显式
SET lock_timeout = '3s',单位支持ms/s/min,超时报canceling statement due to lock timeout - 应用层兜底:用带超时的驱动(如 Python 的
psycopg2.connect(..., options='-c lock_timeout=3s')),避免事务中混入 HTTP 调用、文件读写等外部 I/O - UPDATE 前可加轻量预检:
SELECT ... FOR UPDATE SKIP LOCKED(MySQL 8.0+/PG)尝试非阻塞获取锁,失败则快速退避
最容易被忽略的一点:所有基于 SQL 的方案都依赖开发者写对语句。只要业务上存在“同一资源只能被一个用户占用”的语义,就必须加唯一索引——这是终极兜底。比如抢座场景,place 字段必须建 UNIQUE 索引,哪怕 UPDATE 漏了 AND user_id IS NULL,后续插入也会被唯一约束拦截。

















