MySQL在并发访问时的锁机制设计已经能够覆盖大部分单机场景下的数据一致性需求,但是否需要引入分布式锁,需要结合具体的部署架构和业务场景来判断。不同场景下的并发冲突范围、数据一致性要求不同,对应的解决方案也存在明显差异。

MySQL自身的并发控制机制
MySQL内置了多层次的并发控制能力,在单机实例场景下可以处理大部分并发访问问题:
- 行级锁:InnoDB引擎默认支持行级锁,在更新数据时只会锁定对应的行记录,不会阻塞其他行的操作,并发性能较好。
- 表级锁:MyISAM引擎使用表级锁,操作时会锁定整张表,并发性能较低,适合读多写少的场景。
- 事务隔离级别:通过READ COMMITTED、REPEATABLE READ等隔离级别,控制事务之间的可见性,避免脏读、不可重复读等问题。
- MVCC多版本并发控制:InnoDB通过MVCC实现非锁定读,读操作不会加锁,进一步提升并发性能。
不需要加分布式锁的场景
单机MySQL实例场景
当业务只部署了单个MySQL实例,所有并发请求都访问同一个数据库实例时,不需要引入分布式锁。此时MySQL自身的锁机制和事务能力已经可以保证数据一致性。
例如用户账户余额扣减场景,使用行级锁加事务即可保证正确性:
-- 开启事务 START TRANSACTION; -- 查询并锁定当前用户的余额行 SELECT balance FROM user_account WHERE user_id = 1001 FOR UPDATE; -- 计算扣减后的余额 SET @new_balance = @balance - 50; -- 更新余额 UPDATE user_account SET balance = @new_balance WHERE user_id = 1001; -- 提交事务,释放行锁 COMMIT;
读多写少且允许短暂不一致的场景
如果业务对数据一致性要求不高,允许短暂的数据不一致,比如商品浏览量统计、文章阅读量统计等场景,也不需要加分布式锁。可以通过MySQL的乐观锁机制实现:
-- 乐观锁实现,通过版本号判断是否有并发修改 UPDATE article SET read_count = read_count + 1, version = version + 1 WHERE article_id = 2001 AND version = 当前查询到的版本号;
如果更新返回的影响行数为0,说明有其他请求同时修改了该记录,可以选择重试或者忽略本次更新。
需要加分布式锁的场景
多实例MySQL集群场景
当业务部署了多个MySQL实例,比如分库分表、主从分离且写操作可能落到不同实例的场景,MySQL自身的锁机制无法跨实例生效,此时需要引入分布式锁保证跨实例的并发一致性。
例如分布式系统中生成全局唯一订单号的场景,多个服务实例同时操作不同的订单库,需要保证订单号不重复:
import redis.clients.jedis.Jedis;
import java.util.UUID;
public class OrderIdGenerator {
private Jedis jedis;
private static final String LOCK_KEY = "order_id_generate_lock";
private static final int LOCK_EXPIRE_TIME = 3000; // 锁过期时间3秒
public OrderIdGenerator(Jedis jedis) {
this.jedis = jedis;
}
public String generateOrderId() {
String requestId = UUID.randomUUID().toString();
// 尝试获取分布式锁
String result = jedis.set(LOCK_KEY, requestId, "NX", "PX", LOCK_EXPIRE_TIME);
if ("OK".equals(result)) {
try {
// 获取锁成功,执行订单号生成逻辑
// 这里可以查询多个订单库的最大订单号,生成新的唯一订单号
String orderId = "ORD" + System.currentTimeMillis();
return orderId;
} finally {
// 释放锁,保证删除的是自己加的锁
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
jedis.eval(script, 1, LOCK_KEY, requestId);
}
} else {
// 获取锁失败,重试或者返回异常
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return generateOrderId();
}
}
}
跨系统操作需要保证原子性的场景
当并发操作不仅涉及MySQL,还涉及其他存储系统比如Redis、MongoDB,或者需要调用第三方接口时,需要分布式锁保证整个操作的原子性。例如用户下单时需要同时扣减MySQL中的库存、扣减Redis中的优惠券、调用支付接口,这些操作需要作为一个整体保证一致性。
场景判断总结
可以通过以下维度判断是否需要加分布式锁:
| 判断维度 | 不需要分布式锁 | 需要分布式锁 |
|---|---|---|
| MySQL部署架构 | 单机实例 | 多实例集群、分库分表 |
| 操作范围 | 仅单个MySQL实例内的操作 | 跨实例、跨系统操作 |
| 一致性要求 | 允许短暂不一致、最终一致即可 | 强一致性要求 |
| 并发冲突范围 | 单实例内的并发 | 多服务实例、多节点的并发 |
在实际开发中,优先使用MySQL自身的锁机制和事务能力,只有在跨实例、跨系统的并发场景下,再考虑引入分布式锁,避免不必要的性能损耗。