select for update必须在显式事务中生效,因mysql默认autocommit=1,单独执行会立即提交导致锁瞬时释放;需begin开启事务,锁持续至commit或rollback。

PHP本身不提供数据库加锁能力,加锁完全由MySQL服务端在执行SQL时动态完成;关键在于你发什么语句、在什么上下文(事务/隔离级别/索引)下执行。
SELECT FOR UPDATE 必须在事务中才生效
单独执行 SELECT * FROM orders WHERE id = 123 FOR UPDATE 不会锁住任何行——因为 autocommit 默认开启,语句一执行完就自动提交,锁也立刻释放。
- 必须先显式开启事务:
BEGIN或START TRANSACTION,再执行该语句 - 用 PDO 时记得关掉模拟预处理:
PDO::ATTR_EMULATE_PREPARES => false,否则FOR UPDATE可能被忽略 - 事务结束后(
COMMIT或ROLLBACK),行锁和间隙锁才会释放;中途断开连接也会触发隐式回滚并释放锁
UPDATE/DELETE 自动加X锁,但极易误锁全表
UPDATE 和 DELETE 在事务中执行时默认走当前读,会对扫描到的每条匹配记录加排他锁(X锁),但是否“只锁一行”取决于 WHERE 条件能否命中索引。
- WHERE 使用主键或唯一索引(如
WHERE order_no = 'ORD-2026-001')→ 精确锁单行 - WHERE 使用非唯一索引(如
WHERE status = 'pending')→ 锁所有扫描到的索引项 + 相邻间隙(next-key lock) - WHERE 字段没索引 → 全表扫描,为每条聚簇索引记录加 X 锁,等效于锁整张表,高并发下直接卡死
LOCK TABLES 是表级锁,慎用于InnoDB业务表
LOCK TABLES orders WRITE 这类语句会阻塞其他所有写操作,且与 InnoDB 行锁机制不兼容;它绕过事务系统,也不受隔离级别控制。
- 仅适合极短时间的维护操作(如备份前冻结表),不适合业务逻辑中的并发控制
- 一旦执行,当前会话只能访问被锁定的表,其他表会报错
Table 'xxx' was not locked with LOCK TABLES - PHP 中调用后必须配对
UNLOCK TABLES,否则后续查询会持续失败
GET_LOCK() 是应用层命名锁,不参与事务
MySQL 提供的 GET_LOCK('pay_process_123', 10) 返回 1 表示获取成功,0 表示超时失败;它独立于 InnoDB 存储引擎,不和行数据绑定,也不随事务提交而释放。
- 适合协调跨请求的临界区(如防止重复下单),但需自行管理超时和释放
- 锁名是字符串,建议带业务前缀+唯一标识(如用户ID、订单号),避免冲突
- 必须显式调用
RELEASE_LOCK('pay_process_123'),否则锁会一直持有直到连接关闭
真正难的不是“怎么加锁”,而是判断该不该加、加在哪一层(DB 行锁 / 应用命名锁 / 文件锁)、以及如何验证锁是否按预期生效——比如用 SELECT * FROM INNODB_TRX 查当前事务,或故意用另一个会话尝试更新同一行看是否阻塞。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











