MySQL 5.7 不适合用存储过程封装完整分布式锁逻辑,因其无法自主控制事务生命周期与连接状态,SELECT FOR UPDATE 依赖外部显式事务,GET_LOCK() 仅限会话级、不跨服务,且缺乏原子性超时判断和崩溃自动清理能力;可靠方案是应用层控制事务,配合唯一索引锁表与 holder_id 全局标识实现。

MySQL 5.7 不适合用存储过程封装完整的分布式锁逻辑,因为缺乏原子性保障、无法可靠处理超时与崩溃释放、且 SELECT FOR UPDATE 在存储过程中受事务边界和调用上下文限制严重。真要实现,必须绕过存储过程,直接在应用层控制事务生命周期。
为什么不能把分布式锁逻辑全写进存储过程
存储过程在 MySQL 5.7 中无法自主控制连接生命周期和事务提交时机。常见错误是:在存储过程中执行 SELECT FOR UPDATE 后,以为“锁住了”,但调用方未显式开启事务或提前提交,导致锁立即释放;更糟的是,若存储过程里没配 DECLARE EXIT HANDLER,异常一出事务就隐式提交,锁彻底失效。
另外,MySQL 5.7 的 GET_LOCK() 是会话级的,不同连接间不共享,根本不能用于跨服务协调 —— 这个函数只适合单机内部线程同步,不是分布式锁。
- 存储过程内无法感知外部连接断开或超时,没法自动续期或清理
-
SELECT ... FOR UPDATE必须在显式事务中才有效,而存储过程调用本身不等于开启事务 - 没有可靠的锁过期机制:MySQL 5.7 不支持
INSERT ... ON DUPLICATE KEY UPDATE带条件更新时间戳的原子判断(需要VALUES()支持,该特性在 5.7 中已存在,但组合判断仍非原子)
可行的最小化方案:用表+唯一索引+应用层事务
这是 MySQL 5.7 下最稳定、可验证的落地方式,核心是把“获取锁”和“释放锁”拆成两个独立 SQL,并由应用代码包在同一个事务里执行。
先建锁表:
CREATE TABLE `distributed_lock` ( `lock_key` VARCHAR(128) PRIMARY KEY, `holder_id` VARCHAR(64) NOT NULL, `expire_at` DATETIME NOT NULL, `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB;
获取锁(应用层需开启事务后执行):
INSERT INTO distributed_lock (lock_key, holder_id, expire_at)
VALUES ('order_123', 'svc-a-7f3e', DATE_ADD(NOW(), INTERVAL 30 SECOND))
ON DUPLICATE KEY UPDATE
holder_id = VALUES(holder_id),
expire_at = VALUES(expire_at),
created_at = VALUES(created_at);
紧接着立刻查是否真正拿到锁:
SELECT 1 FROM distributed_lock WHERE lock_key = 'order_123' AND holder_id = 'svc-a-7f3e' AND expire_at > NOW();
- 返回一行 → 锁获取成功,继续业务逻辑
- 返回空 → 竞争失败,可重试或拒绝
释放锁(同样在事务内):
DELETE FROM distributed_lock WHERE lock_key = 'order_123' AND holder_id = 'svc-a-7f3e';
容易被忽略的三个致命细节
第一,holder_id 必须全局唯一且带进程/实例标识(如 hostname:pid:thread_id),否则服务重启后可能误删别人持有的锁。
第二,应用层必须设置合理的事务超时(如 innodb_lock_wait_timeout=10),避免死等;同时自身要有重试退避逻辑,不能无限循环 INSERT ... ON DUPLICATE KEY UPDATE。
第三,永远不要依赖 expire_at 字段自动清理 —— MySQL 5.7 没有轻量级定时任务机制,得靠应用定期扫描过期行并 DELETE,或用外部调度器(如 cron + 脚本)补漏。











