
GET_LOCK() 为什么经常“锁不住”业务逻辑
因为 GET_LOCK() 是会话级的,不是事务级的;一旦客户端连接断开(比如超时、异常退出),锁自动释放,但你的业务代码可能还在跑。更麻烦的是,它不和 InnoDB 行锁协同——你用 GET_LOCK('order_123') 加了应用层锁,但没对 orders 表加行锁,其他事务照样能 UPDATE 同一行,造成逻辑错乱。
常见错误现象:SELECT GET_LOCK('key', 0) 返回 1,以为锁成功,结果并发请求都进了临界区;或者锁住后忘了调 RELEASE_LOCK(),导致后续请求永远阻塞(尤其在连接池复用场景下)。
-
GET_LOCK()的锁名是全局字符串,大小写敏感,且会被截断到 64 字符,超长名实际等价 - 第二个参数是等待秒数:
0表示不等待直接返回,NULL表示无限等待(危险!) - 锁不会随事务提交/回滚释放,只随连接关闭或显式调用
RELEASE_LOCK()释放 - MySQL 8.0+ 默认禁用该函数(
secure_file_priv无关,但需确认have_symlinks=NO不影响,真正限制来自disabled_storage_engines?不,是performance_schema级别开关——其实只要没显式禁用,通常可用)
如何安全地配合事务使用 GET_LOCK()
不能只靠 GET_LOCK() 拦住并发,必须把它当作“准入检查”,真正的数据一致性还得靠 InnoDB 自身的锁机制。典型做法:先抢应用锁,再查+更新数据,并确保在同一个事务里完成。
示例场景:扣减库存,防止超卖
START TRANSACTION;
SELECT GET_LOCK('stock_item_456', 10) AS lock_acquired;
-- 检查 lock_acquired == 1,否则直接 ROLLBACK
SELECT stock FROM items WHERE id = 456 FOR UPDATE;
UPDATE items SET stock = stock - 1 WHERE id = 456 AND stock >= 1;
-- 若影响行为 0,说明库存不足,ROLLBACK 并 RELEASE_LOCK('stock_item_456')
COMMIT;
SELECT RELEASE_LOCK('stock_item_456');- 必须在事务开启后、执行 DML 前调用
GET_LOCK(),避免锁住后事务卡住导致锁长期占用 -
FOR UPDATE不可省略——这是真正防止并发修改的保障,GET_LOCK()只是减少无谓的行锁竞争 -
RELEASE_LOCK()要放在事务外执行(如应用层),因为事务提交后连接可能复用,锁状态不会自动清理 - 如果应用崩溃,锁会在连接关闭时自动释放,但依赖连接池配置(如 HikariCP 的
connection-timeout和leak-detection-threshold)
替代方案比 GET_LOCK() 更可靠吗
大多数情况下,是的。比如用唯一索引+INSERT IGNORE 实现幂等占位,或用 Redis 的 SET key value NX EX 30 做分布式锁——它们失败反馈明确、超时自动释放、不依赖 MySQL 连接生命周期。
但如果你的业务完全不跨服务、只在单 MySQL 实例内协调,且已用连接池控制生命周期,GET_LOCK() 仍可作为轻量辅助手段。只是别把它当主力锁机制。
- InnoDB 行锁 + 事务隔离级别(如
READ COMMITTED)通常比应用层锁更高效、更可控 - Redis 锁需要处理客户端宕机、网络分区、时钟漂移等问题,
GET_LOCK()虽简单,但“简单”不等于“安全” - MySQL 8.0 引入了
SELECT ... FOR UPDATE SKIP LOCKED,适合队列类场景,比手动抢锁更自然
RELEASE_LOCK() 返回 NULL 是什么情况
不是出错,而是表示“当前会话没持有这个锁”。常见于:锁被别的会话持有,你调 RELEASE_LOCK() 时自己根本没拿到过;或者之前调过 GET_LOCK() 但返回 0(获取失败),之后又去释放。
这说明你在释放前没校验锁是否真被自己拿到,容易掩盖逻辑漏洞。
- 务必检查
GET_LOCK()返回值,只在返回 1 时才继续后续操作 - 不要在存储过程中无条件调
RELEASE_LOCK(),应先用IS_USED_LOCK('key')查是否本会话持有(返回 connection_id) -
IS_FREE_LOCK('key')返回 1 表示没人持锁,但它不告诉你谁持有,也不保证你紧接着能抢到——竞态依然存在
事情说清了就结束。真正难的不是调哪个函数,而是想清楚:你要锁住的到底是什么——是数据库里的一行数据,还是应用里一个瞬时状态?前者交给 InnoDB,后者才轮到 GET_LOCK() 出场,而且得兜底。










