基于数据库的分布式锁
基于数据库的分布式锁主要通过数据库的约束或事务机制实现,主要分为以下三种方式:
一、基于唯一索引/唯一约束的分布式锁
实现原理
• 创建一张锁表(如lock_table
),包含字段lock_key
(唯一索引)和expire_time
(锁过期时间)。• 加锁时插入一条
lock_key
对应的记录,利用唯一索引的排他性确保互斥;解锁时删除该记录。• 若插入失败,则表明锁已被占用,需等待或重试。
优缺点
• 优点:实现简单,无需额外中间件;强一致性保障。• 缺点:
◦ 性能低(QPS通常低于1k)。
◦ 死锁风险:若客户端宕机未释放锁,需通过定时任务清理过期锁记录。
◦ 需处理锁重入问题,可通过添加
owner
字段标识锁持有者。适用场景
• 低频、高一致性要求的场景,如金融对账、低频任务调度。
二、基于数据库悲观锁的分布式锁
实现原理
• 使用SELECT ... FOR UPDATE
对指定行加行级锁,事务提交后自动释放。• 例如:
SELECT * FROM lock_table WHERE lock_key = 'xxx' FOR UPDATE
。优缺点
• 优点:强一致性,天然支持事务隔离。• 缺点:
◦ 性能较差(频繁的数据库IO操作)。
◦ 锁表风险:若未合理设计索引,可能导致表锁而非行锁。
◦ 需显式管理事务,避免长事务阻塞其他请求。
适用场景
• 需要严格事务控制的场景,如库存扣减(低频高一致性)。
三、基于数据库乐观锁的分布式锁
实现原理
• 在业务表中添加version
字段,更新时通过CAS(Compare and Swap)机制判断版本号是否一致。• 示例SQL:
UPDATE table SET count = count - 1, version = version + 1 WHERE id = #{id} AND version = #{old_version}
1
2若更新失败,则重试或放弃。
优缺点
• 优点:无锁竞争,适合读多写少场景;轻量级。• 缺点:
◦ 需业务层处理重试逻辑,增加复杂度。
◦ 高并发场景下频繁重试可能降低性能。
适用场景
• 并发冲突较少的场景,如账户余额更新、抢券等。
四、优化与注意事项
锁超时机制
• 添加expire_time
字段,通过定时任务清理过期锁,避免死锁。客户端唯一标识
• 使用owner
字段记录锁持有者(如服务名+IP+线程ID),确保只有持有者能释放锁。性能优化
• 减少锁粒度(如行级锁)。• 结合本地缓存减少数据库访问频率。
容错与重试
• 设置最大重试次数,避免无限等待。
五、总结与选型建议
实现方式 | 适用场景 | 性能 | 复杂度 |
---|---|---|---|
唯一索引 | 低频强一致(如金融对账) | 低 | 低 |
悲观锁(FOR UPDATE) | 短事务、高一致性需求 | 中 | 中 |
乐观锁 | 低冲突、读多写少场景 | 高 | 高 |
建议:
• 若系统已依赖数据库且并发量低,优先选择唯一索引或悲观锁。
• 高并发场景建议结合Redis或ZooKeeper实现分布式锁,避免数据库成为瓶颈。