CodeIgniter中LOCK TABLES不可靠,因其与CI事务机制及持久连接冲突,易致全表阻塞、死锁或锁滞留;应改用SELECT ... FOR UPDATE行级锁或Redis分布式锁。

CodeIgniter 本身不支持分布式锁,强行在 CI 中用 LOCK TABLES 做并发控制,大概率会锁住整个表、阻塞请求、甚至引发死锁——这不是配置问题,是架构冲突。
为什么 LOCK TABLES 在 CodeIgniter 里根本不可靠
CI 的 Query Builder 和事务机制($this->db->trans_start())与 MySQL 表级锁天然互斥。InnoDB 引擎下,一旦你在事务中执行 LOCK TABLES users WRITE,后续所有 DML 语句都会挂起,直到锁释放或超时;而 CI 默认开启持久连接(pconnect = TRUE),锁会绑定在连接上,一个请求没走完 UNLOCK TABLES,整个连接池就可能被拖住。
- 错误现象:
SHOW PROCESSLIST中大量线程状态为Locked或Sleep但Command是Query,响应时间阶梯式上涨 - 不能依赖 try/catch:异常抛出后若没走到
$this->db->query("UNLOCK TABLES"),锁不会自动释放 - CI 日志不记录锁状态,必须直连 MySQL 查
information_schema.PROCESSLIST或SHOW OPEN TABLES WHERE In_use > 0
替代方案:用数据库行级锁(SELECT ... FOR UPDATE)代替表锁
如果你的场景是“检查邮箱是否存在 + 插入用户”,且表有主键或唯一索引(如 email 字段加了 UNIQUE),优先走 InnoDB 的行级锁,而不是自己扛着表锁硬上。
- 必须在事务内执行:
$this->db->trans_start()→$this->db->query("SELECT id FROM users WHERE email = ? FOR UPDATE", [$email])→ 判断结果 →INSERT→$this->db->trans_complete() - 不要手动写
LOCK TABLES,CI 的FOR UPDATE支持是隐式通过原生 SQL 实现的,Query Builder 不提供封装,但可安全调用 - 失败时事务自动回滚,不会留下悬挂锁;锁粒度只落在匹配的行上,不影响其他注册请求
- 注意:
FOR UPDATE只对已存在的行加锁,如果邮箱不存在,它不锁任何行,此时插入仍需靠唯一索引约束兜底防重复
真正需要分布式锁时,别在 CI 层硬搞
当业务跨多个服务(比如 PHP + Node.js + Java 同时写同一张表)、或涉及定时任务去重、或要协调缓存更新时,LOCK TABLES 和 FOR UPDATE 都失效——它们只在单数据库连接内有效,无法跨进程、跨语言、跨网络。
- 这时候该上 Redis 分布式锁,比如用
SET key value NX EX 30加锁,value 设为请求唯一标识(如md5($ip . $uri . time())),释放时先 GET 再 DEL 校验,避免误删 - CI 中可用
$this->redis->set($key, $value, ['nx', 'ex' => 30])(需自行集成 Redis 客户端),但锁的生命周期必须由业务代码严格管理,不能交由 CI 生命周期 - 更稳妥的做法是把锁逻辑下沉到独立服务或中间件,CI 只负责调用接口(如
POST /lock/acquire),避免锁状态和框架耦合
最常被忽略的一点:锁不是越早加越好,而是要卡在「真正需要互斥」的最小窗口。比如注册流程中,检查邮箱和插入之间才需要锁,而不是从接收请求就开始锁表。粒度错位,性能和稳定性都会崩。


















