跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

CodeIgniter如何处理分布式锁_CodeIgniter并发控制策略【并发】

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
),避免锁状态和框架耦合 最常被忽略的一点:锁不是越早加越好,而是要卡在「真正需要互斥」的最小窗口。比如注册流程中,检查邮箱和插入之间才需要锁,而不是从接收请求就开始锁表。粒度错位,性能和稳定性都会崩。

相关文章