php不直接实现行锁或表锁,锁由数据库提供,php仅通过pdo/mysqli发送sql触发;表锁锁定整张表,行锁仅锁匹配行,innodb默认行锁但索引失效会退化为表锁;需手动开启事务并执行select...for update等显式加锁;行锁更易死锁,应捕获异常并重试;选型应依场景:高频小粒度用行锁+乐观锁,大范围读多写少用表锁或快照读。

PHP 本身不直接实现行锁或表锁,锁机制由底层数据库(如 MySQL)提供,PHP 只是通过扩展(如 PDO、mysqli)发送 SQL 命令来触发和管理锁。理解行锁与表锁的区别,关键在于掌握它们在数据库层面的行为、适用场景及潜在风险。
锁粒度不同:影响并发性能的核心差异
表锁(Table-level Lock)锁定整张表,所有对该表的写操作(INSERT/UPDATE/DELETE)都会被阻塞,读操作是否阻塞取决于锁类型(如 READ LOCK 允许并发读,WRITE LOCK 则完全排斥其他读写)。行锁(Row-level Lock)只锁定满足条件的具体数据行,其余行仍可被其他事务访问,大幅提升了高并发下的吞吐量。
- MyISAM 引擎只支持表锁,InnoDB 默认使用行锁(基于索引实现)
- 即使使用 InnoDB,若 WHERE 条件未命中索引,可能退化为表级锁(如全表扫描时加锁范围扩大)
- 执行 SELECT ... FOR UPDATE 或 SELECT ... LOCK IN SHARE MODE 才会真正触发行锁,普通 SELECT 不加锁
使用方式不同:PHP 中如何显式加锁
在 PHP 中需手动开启事务并执行带锁的 SELECT,才能生效。自动提交(autocommit)开启时,每条语句独立提交,锁无法跨语句保持。
- 必须先调用 $pdo->beginTransaction() 或 $mysqli->begin_transaction()
- 执行带锁查询,例如:SELECT * FROM orders WHERE id = 123 FOR UPDATE
- 后续对同一行的 UPDATE/DELETE 会被当前事务持有锁保护,其他事务需等待
- 最后用 commit() 释放锁;若出错应 rollback(),否则锁可能长时间滞留
死锁风险与排查要点
行锁因加锁顺序复杂、粒度细,更容易出现死锁(如事务 A 锁住行1再申请行2,事务 B 同时锁住行2再申请行1)。表锁因粒度粗、顺序简单,死锁概率极低,但并发能力差。
立即学习“PHP免费学习笔记(深入)”;
- InnoDB 会主动检测死锁,并回滚其中代价较小的事务(抛出 Deadlock found when trying to get lock 错误)
- PHP 中应捕获该异常,加入重试逻辑(如最多尝试 3 次)
- 通过 SHOW ENGINE INNODB STATUS 查看最近死锁详情,定位冲突 SQL 和事务ID
实际选型建议:别为了“高级”而滥用行锁
不是所有场景都适合行锁。过度依赖行锁可能引发锁争用、间隙锁(Gap Lock)误伤、或因索引失效导致意外表锁。
- 计数器更新、库存扣减等高频小粒度修改 → 优先用行锁 + 乐观锁(version 字段)或数据库原子操作(UPDATE stock SET num = num - 1 WHERE id = 1 AND num >= 1)
- 后台批量导出、日志归档等读多写少且范围大 → 表锁或无锁快照读(READ COMMITTED 隔离级别下 SELECT 不阻塞)更合适
- 业务逻辑涉及多表关联更新时,务必按固定顺序访问表,避免交叉加锁











