高并发场景下有哪些数据防重的方案?

1. 需求背景

在实际的业务开发过程中,很多时候都需要保证数据的唯一性。比如我们在做的SAAS系统,租户有对应的配置数据,一个租户只能有一条数据。可以通过以下两个步骤来判断:

  1. 先查询这个租户是否存在这条配置数据?
  2. 如果没有则插入,有则忽略。

在并发低的场景是没有问题的,但是在高并发的场景下,两线程同时查到该条数据不存在,然后接着执行写入的逻辑,这样就导致了数据重复。

2. 解决方案

2.1 分布式锁

使用Redis分布式锁,所有的请求都串行化执行了。具体实现方式有几种:

  • setNX代码方式
  • Redission
  • lock4j

使用这种方式要注意锁的粒度不能太大,

2.2 数据库实现

2.2.1 唯一索引

给表建立相应的唯一索引,比如:uk_tenant_id。这样可以保证一个租户只有一条数据。适用于不需要逻辑删除的业务

2.2.2 insert ignore写入

使用insert ... ignore,mysql发现如果数据重复就忽略,否则就会插入。要求表中存在唯一索引或PRIMARY KEY。也不适用于逻辑删除的业务。

PS:使用insert ... ignore也有可能会导致死锁。

2.2.3 insert on duplicate key update写入

使用insert on duplicate key update,数据不存在则直接insert。如果发现数据已经存在了,则update。不过要求表中存在唯一索引或PRIMARY KEY。

PSinsert on duplicate key update在高并发的情况下,可能会产生死锁问题

2.2.4 增加一张防重表(业务)

CREATE TABLE `config_unique` (
  `id` bigint(20) NOT NULL COMMENT 'id',
  `config_name` varchar(130) DEFAULT NULL COMMENT '名称',
  `config_key` varchar(255) NOT NULL COMMENT '键',
  `tenant_id` bigint(20) unsigned NOT NULL COMMENT '租户id',
  `user_name` varchar(30) NOT NULL COMMENT '创建用户名称',
  `create_date` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_configName_configKey` (`config_name`,`config_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配置防重表';

注意表里并没有逻辑删除字段deleted。在添加配置数据之前,先添加防重表。如果添加成功,则说明可以正常添加配置,如果添加失败,则说明有重复数据。保证防重表和添加配置的操作必须要在同一个事务中,否则会出问题。

在代码中:

try {
    
    
  transactionTemplate.execute((status) -> {
    
    
      configUniqueMapper.insert(configUnique);
      configMapper.insert(config);
  return Boolean.TRUE;
  });
} catch(DuplicateKeyException e) {
    
    
   config = configMapper.query(config);
}

捕获到DuplicateKeyException 异常则说明已经插入过数据。

3. 总结

根据业务场景可以选择对应的方案进行数据防重处理,没有最好,只有合适的方案。

猜你喜欢

转载自blog.csdn.net/oschina_41731918/article/details/127283452