GET_LOCK()适合单库单连接场景,会话级命名锁,连接断开自动释放;唯一索引表方案支持跨进程、可重入、带过期,需校验lock_value防误删。

用 GET_LOCK() 实现最简分布式锁,但仅限单库单连接场景
MySQL 内置的 GET_LOCK() 是最轻量、零建表、自动释放的方案,适合定时任务防重或短时临界区保护。它本质是会话级命名锁,连接断开即释放,天然规避服务宕机不释放导致的死锁。
常见错误是直接在长事务里调用它——GET_LOCK() 不参与事务,也不受隔离级别影响,但它和事务共用同一个连接;若事务未提交而连接被复用,锁可能意外丢失。另外,它不支持锁续期,超时后无法判断是否仍被持有。
- 获取锁:执行
SELECT GET_LOCK('order_sync_lock', 0),返回1表示成功,0表示失败(非阻塞) - 释放锁:显式调用
SELECT RELEASE_LOCK('order_sync_lock'),或让连接关闭(如连接池回收、应用重启) - 注意:
GET_LOCK()锁名区分大小写,且最大长度为 64 字符;多个并发调用同一锁名时,只有一个能返回1
用唯一索引表实现可重入、带过期的分布式锁
这是生产环境最主流的方案,核心靠 INSERT ... ON DUPLICATE KEY UPDATE 的原子性 + expire_time 字段做兜底。它不依赖连接生命周期,支持跨进程、跨机器,也便于扩展可重入和续期逻辑。
容易踩的坑是没处理“锁被别人续期”问题:比如 A 拿到锁后业务慢,B 在锁快过期时用相同 lock_key 执行 ON DUPLICATE KEY UPDATE 续了期,但后续 A 仍按原 lock_value 去删锁,就会删掉 B 的锁——造成误释放。所以释放锁必须校验 lock_value(即客户端唯一标识)。
- 建表必须含
UNIQUE KEY uk_lock_key (lock_key),主键可为自增也可为lock_key - 加锁语句建议用
INSERT INTO distributed_lock (lock_key, lock_value, expire_time) VALUES (?, ?, DATE_ADD(NOW(), INTERVAL ? SECOND)) ON DUPLICATE KEY UPDATE lock_value = VALUES(lock_value), expire_time = VALUES(expire_time) - 释放锁必须用
DELETE FROM distributed_lock WHERE lock_key = ? AND lock_value = ?,不能只删lock_key - 定期清理过期锁可用
DELETE FROM distributed_lock WHERE expire_time ,但需注意该语句本身可能引发长事务或锁竞争
为什么不用 SELECT ... FOR UPDATE 做分布式锁
很多人第一反应是“既然有行锁,那就查一行再锁住”,但这是高风险做法:它依赖事务边界,一旦业务代码异常退出、事务未提交或连接中断,锁就一直挂着,极易演变成死锁或资源长期阻塞。
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
更麻烦的是,FOR UPDATE 在可重复读(RR)隔离级别下会加间隙锁(Gap Lock),若锁表无主键或索引不走覆盖,可能锁住整张表;多个服务按不同顺序访问同一行(比如 A 先锁 order_id=10086 再锁 user_id=5001,B 反过来),就直接触发 InnoDB 死锁检测并回滚——这不是你想要的“锁失败”,而是不可控的事务中断。
-
FOR UPDATE适合业务强耦合数据库事务的场景(如扣库存+写订单在同一个事务内),不适合通用分布式锁 - 若坚持用,务必确保:查询条件命中唯一索引、事务粒度极小、有完善的超时与重试机制
- 永远不要在连接池环境中对
FOR UPDATE结果做长时间处理,连接归还池后锁不一定释放(取决于 MySQL 配置和驱动行为)
死锁预防的关键不在锁本身,而在访问顺序与超时控制
MySQL 分布式锁本身不会“导致”死锁,真正引发死锁的是多个服务在操作共享资源时,对数据库表/行的访问顺序不一致,叠加事务未及时提交。比如锁服务 A 更新 orders 表后再更新 stocks 表,而锁服务 B 反过来先 stocks 后 orders,哪怕它们用的不是同一个锁表,只要事务交叉,就可能形成循环等待。
所以比选哪种锁方案更重要的是:统一所有服务对核心表的 DML 顺序、把锁粒度控制在最小必要范围、设置合理的锁超时(建议为业务最大耗时的 2–3 倍)、避免在持锁期间调用外部 HTTP 或 RPC。
- 锁超时时间设太短:业务未完成锁已失效,其他节点抢入 → 数据错乱
- 锁超时时间设太长:节点宕机后锁长期不释放 → 资源不可用
- 没有校验
lock_value就删锁:A 和 B 用相同 key 加锁,B 续期后 A 仍删锁 → 误释放 - 在锁内执行慢查询或大事务:拖长持有时间,放大冲突概率
实际落地时,GET_LOCK() 适合简单、短平快的场景;唯一索引表方案需要多写几行代码,但可控性强;至于 FOR UPDATE,除非你清楚知道每一行锁的范围和生命周期,否则别碰。真正的难点从来不是“怎么加锁”,而是“怎么让所有服务遵守同一套资源访问契约”。










