mysql的select ... for update仅在where条件命中索引且执行计划显示type为const/ref/range时才加行锁;否则可能升级为表锁或间隙锁。
mysql 的 select ... for update 在什么场景下才真正加行锁?
不是所有带 select ... for update 的语句都会锁住具体行——它只在明确命中索引且走索引扫描时生效。如果查询条件没走索引(比如对非索引字段 where status = 'pending'),innodb 会升级为表锁或间隙锁,导致其他事务被意外阻塞。
- 必须确保
WHERE条件中的字段有有效索引(唯一索引最佳) - 用
EXPLAIN看执行计划,确认type是const、ref或range,而非ALL - 联合索引要注意最左前缀:写
WHERE user_id = 123 AND order_id = 456可以锁行;但只写WHERE order_id = 456就可能失效 - 事务隔离级别需为
REPEATABLE READ(MySQL 默认),READ COMMITTED下部分场景会降级为记录锁+间隙锁组合
PostgreSQL 怎么避免 UPDATE ... WHERE id = ? 引发的竞态更新?
直接执行 UPDATE users SET balance = balance - 100 WHERE id = 123 看似安全,但如果多个请求同时读到旧余额再各自减,就会漏掉并发校验。PostgreSQL 提供更稳妥的原子路径,但得配合应用层逻辑。
- 优先用
UPDATE ... RETURNING拿回更新后的值,再判断是否符合业务预期(比如余额不能为负) - 若需强一致性校验,加
AND balance >= 100到WHERE子句中,然后检查rows_affected是否为 1 - 不要依赖
SELECT + UPDATE两步操作,中间没有锁保护,必然出现“先查后扣”超支 - 注意
UPDATE本身会对匹配行自动加行级排他锁,但仅限于 WHERE 命中的行,不会锁全表
用户权限隔离和行级锁是两回事,别混着用
给用户分配 SELECT 或 UPDATE 权限,控制的是“能不能执行某类 SQL”,跟“多用户同时改同一行会不会冲突”完全无关。权限不解决并发问题,锁机制才管这个。
- 即使 A 用户只有
UPDATE权限、B 用户只有SELECT权限,只要他们连的是同一个数据库账号(或权限等效账号),照样会因行锁互相等待 - 按租户隔离数据时,常见做法是在每张表加
tenant_id字段并建联合索引,再让所有 SQL 自动带上WHERE tenant_id = ?——这不是权限控制,而是通过查询条件缩小锁范围 - 应用层做“谁只能改谁的数据”校验,必须在事务开始前完成,不能靠数据库权限兜底
事务太长会让行锁持有太久,比锁本身更危险
一个事务里先 SELECT ... FOR UPDATE,然后调第三方 API、生成 PDF、发邮件……最后才 COMMIT,这期间那几行一直被锁着,其他用户全卡住。锁时间取决于事务持续时间,不是 SQL 执行时间。
- 把耗时操作(IO、网络、计算)全部挪到事务外,只留纯粹的 DB 读写在里面
- 必要时拆成两个事务:第一个只做锁和校验,拿到结果后立即提交;第二个根据结果做副作用操作
- 监控
information_schema.INNODB_TRX表,定期查TRX_STARTED和TRX_STATE,揪出长时间未提交的事务 - 设置
innodb_lock_wait_timeout(MySQL)或lock_timeout(PG),让等待过久的请求快速失败,而不是无限挂起
事务边界模糊、锁粒度误判、权限与并发机制混淆——这三个点最容易在上线后突然冒出来,而且一出就是线上故障。










