MySQL 的 GET_LOCK() 不适合生产级分布式锁,因其是会话级命名信号量,无持有者标识、依赖连接断开释放、无超时清理机制且不校验调用方身份;应改用唯一索引表方案,通过 INSERT 原子性争抢锁,并校验 client_id 释放。

MySQL 的 GET_LOCK() 不适合做生产级分布式锁,尤其在连接池、高并发或服务异常场景下极易出错。 它本质是会话级“命名信号量”,不是分布式系统需要的带所有权、超时、可重入、防误删的锁。真要靠 MySQL 做分布式锁,该用唯一索引表方案。
为什么 GET_LOCK() 在分布式场景下不可靠
它不解决分布式锁最核心的几个问题:
-
GET_LOCK()锁名全局可见但无持有者标识——A 服务拿到锁后崩溃,B 服务无法判断该锁是否“已失效”,只能干等连接超时释放(可能长达几分钟) - 锁自动释放依赖连接断开,而连接池(如 HikariCP)会复用连接——
GET_LOCK('order_123', 10)成功后,连接归还池中,锁仍被“挂着”,后续请求全被阻塞 - 没有超时自动清理机制:第二个参数设为
NULL会无限等待;设为具体秒数(如5)又无法覆盖业务执行波动(比如 GC 暂停导致操作卡住 6 秒) - 多个服务实例可能同时
SELECT GET_LOCK(...)返回1——因为 MySQL 不校验调用方身份,只看锁名是否空闲,根本没“租约”概念
如何用唯一索引表安全实现分布式锁
这是目前 MySQL 环境下最可控、无需额外组件的方案,核心靠 INSERT 的原子性和唯一索引冲突来判别锁状态。
- 建表必须含
lock_key唯一索引,并带expire_time字段:CREATE TABLE `distributed_lock` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `lock_key` VARCHAR(64) NOT NULL, `client_id` VARCHAR(64) NOT NULL COMMENT '本机IP+进程ID或UUID', `expire_time` DATETIME NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_lock_key` (`lock_key`) ) ENGINE=InnoDB;
- 获取锁用带过期时间的
INSERT,不依赖事务隔离级别:INSERT INTO distributed_lock (lock_key, client_id, expire_time) VALUES ('order_1001', '10.0.1.5:8080:abc123', DATE_ADD(NOW(), INTERVAL 30 SECOND));若返回影响行数为1→ 获锁成功;若报Duplicate entry 'order_1001' for key 'uk_lock_key'或影响行为0→ 获锁失败 - 释放锁必须校验
client_id,防止误删:DELETE FROM distributed_lock WHERE lock_key = 'order_1001' AND client_id = '10.0.1.5:8080:abc123';
- 定时清理过期锁(可选但推荐):
DELETE FROM distributed_lock WHERE expire_time 建议用单独 job 每 10–15 秒执行一次,避免堆积
GET_LOCK() 唯一可用的场景:单机多线程轻量协调
仅当满足全部以下条件时,才考虑用它:
- 应用部署在单台机器,所有 worker 共享同一个 MySQL 连接(非连接池)
- 临界区逻辑极短(
- 能接受锁在进程 crash 后最多等待连接超时(如 wait_timeout=60s)才释放
- 不需要区分锁持有者,也不关心谁在用这把锁
示例(仅限调试或脚本工具):
SELECT GET_LOCK('cleanup_job', 1); -- 等 1 秒,失败就跳过
-- 执行清理逻辑
SELECT RELEASE_LOCK('cleanup_job');
注意:RELEASE_LOCK() 必须由同一会话调用,跨会话释放始终返回 0。
真正跨服务、需容错、讲 SLA 的分布式锁,GET_LOCK() 的语义缺失太严重。哪怕只是加个 client_id 校验和自动过期,都得退回到唯一索引表方案——那才是把 MySQL 当数据库用,而不是当信号量 API 用。











