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