如何在Python中为MySQL 5.7实现类SELECT ... FOR UPDATE NOWAIT保障初始化脚本仅执行一次
解决方案
首先明确结论:Python侧无法自行实现MySQL原生的NOWAIT逻辑,因为SELECT ... FOR UPDATE的排队阻塞行为是在MySQL服务端生效的,请求发出后Python侧只能等待服务端响应,无法直接跳过锁等待,强制设置查询超时的方案误差大、稳定性差,不推荐使用。
以下是3种MySQL 5.7环境下可落地的替代方案,均能保证初始化逻辑仅执行一次,且无锁排队性能问题:
方案1:使用MySQL原生GET_LOCK函数模拟NOWAIT
MySQL 5.7原生支持用户级锁函数,可实现0等待的锁抢占逻辑:
- 提前约定全局唯一的锁名,例如
your_project_init_lock - 执行SQL
SELECT GET_LOCK('your_project_init_lock', 0),返回值为1代表成功获取锁,返回0代表锁已被其他进程持有,可直接返回 - 成功获锁后查询初始化记录,若仍未完成初始化则执行对应脚本,完成后更新记录状态
- 最后必须执行
SELECT RELEASE_LOCK('your_project_init_lock')释放锁,建议用Python上下文管理器封装,避免异常场景下锁泄漏
注意:
GET_LOCK是连接级别的锁,连接断开后会自动释放,不会出现永久死锁问题。
方案2:基于乐观锁实现无锁抢占
不需要加任何行锁,仅靠数据库原子UPDATE操作即可实现,性能最优:
- 给初始化记录新增
status字段,可选值为未初始化/初始化中/初始化完成 - 所有进程先查询记录状态,若为
初始化完成直接返回 - 若为
未初始化,执行原子UPDATE操作:UPDATE init_record SET status = '初始化中' WHERE id = 唯一记录ID AND status = '未初始化' - 检查UPDATE影响行数:返回
1代表当前进程抢占成功,执行初始化脚本,完成后将status更新为初始化完成;返回0代表已有其他进程抢占成功,直接返回即可
拓展:如果初始化逻辑存在失败概率,可以新增
update_time字段,配合定时任务清理超过超时时间仍处于初始化中的记录,避免单次执行失败后无进程重试。
方案3:引入分布式锁组件
如果项目中已经部署了Redis等缓存组件,也可以用分布式锁实现:
- 执行Redis命令
SET lock_key unique_value NX EX 30,设置成功代表抢占到锁 - 成功获锁后执行初始化逻辑,完成后删除锁即可
内容的提问来源于stack exchange,提问作者Qiulang
相关产品推荐
相关产品推荐

