在codeigniter中防止并发注册重复邮箱,应使用$this->db->query("lock tables users write")显式加表级写锁,检查邮箱并插入后必须调用$this->db->query("unlock tables")释放锁;不可与事务混用,需确保同一连接内执行,避免持久连接导致锁滞留。

CodeIgniter里怎么加表级写锁防止并发注册
直接用 $this->db->query() 执行原生 SQL 加锁,CI 的 Query Builder 不支持 LOCK TABLES 语法,硬套 select()->where()->get() 会失败。
典型做法是:在事务开启前,先手动获取写锁;检查邮箱、插入数据;最后释放锁。整个流程必须在同一个数据库连接内完成,否则锁无效。
-
$this->db->query("LOCK TABLES users WRITE");—— 必须显式调用,不能依赖 CI 自动管理 - 紧接着做
SELECT COUNT(*) FROM users WHERE email = 'xxx'和INSERT INTO users... - 锁必须用
$this->db->query("UNLOCK TABLES");主动释放,CI 不会在请求结束时自动解锁 - 如果中间抛异常没走到
UNLOCK TABLES,锁会一直挂着,拖垮后续所有请求
为什么 LOCK TABLES 在 CodeIgniter 中容易导致死锁或阻塞
根本原因是 CI 默认开启持久连接(pconnect = TRUE),而 MySQL 的表锁绑定在连接生命周期上。一个请求卡住没释放锁,整个连接池里的连接都可能复用这个“带锁状态”的连接。
更隐蔽的问题是:CI 的 $this->db->trans_start() 和 LOCK TABLES 不能混用。InnoDB 事务和表级锁机制冲突——事务内加表锁,会导致后续语句被挂起,甚至触发 Deadlock found when trying to get lock。
- 不要在
trans_start()之后执行LOCK TABLES - 确保
UNLOCK TABLES在同一连接、同一作用域中执行,不能靠 try/catch 外层兜底 - 高并发下,多个请求排队等同一个
users WRITE锁,响应时间会阶梯式上涨,不是报错而是变慢
如何检测当前有没有未释放的表锁
不能靠 CI 日志,得直连 MySQL 查 information_schema。最有效的是查 INNODB_TRX 和 PROCESSLIST,看有没有长时间运行、状态为 Locked 或 Sleep 但 Command 是 Query 的线程。
- 执行
SHOW OPEN TABLES WHERE In_use > 0;—— 看哪些表正被锁着 - 执行
SELECT * FROM information_schema.PROCESSLIST WHERE COMMAND = 'Sleep' AND TIME > 60;—— 找出疑似卡住的连接 - 配合
SHOW ENGINE INNODB STATUS\G看LATEST DETECTED DEADLOCK部分,确认是否已有死锁发生
比 LOCK TABLES 更稳妥的替代方案
表锁太重,实际项目里更推荐用 INSERT ... ON DUPLICATE KEY UPDATE 或 INSERT IGNORE,前提是业务能接受“插入失败时静默跳过”或“用主键/唯一键兜底”。虽然知识库强调“不允许加唯一索引”,但若只是临时规避结构变更,可在应用层建一张轻量 email_locks 表,用 INSERT INTO email_locks (email) VALUES (?) 做分布式锁试探,失败即说明已被占用——这比锁整张 users 表安全得多。
真正难处理的,是那些既不能改表结构、又不允许失败返回、还要求强一致的场景。这时候,表锁不是首选,而是最后手段;你得盯住连接生命周期、超时设置、以及每次 UNLOCK TABLES 是否真被执行到了。











