解决方案:如何防止数据重复插入?
目录
- 为啥要解决数据重复插入?
- 解决方案实战
- 可落地小总结
一、为啥要解决数据重复插入?
问题起源,微信小程序抽风 wx.request() 重复请求服务器提交数据。后端服务也很简单,伪代码如下:
class SignLogService {
public void saveSignLog(SignLogDO log) {
// 简单插入做记录
SignLogDAO.insert(log);
}
}
发现数据库会存在重复数据行,提交时间一模一样。但业务需求是不能有多余的 log 出现,这明显是个问题。
问题是,重复请求导致的数据重复插入。这问题造成的后果很明显:
- 数据冗余,可能不单单多一条
- 有些业务需求不能有多余数据,造成服务问题
问题如图所示:
解决方式:如何将 同请求 A,不执行插入,而是读取前一个请求插入的数据并返回。解决后流程应该如下:
二、解决方案实战
1.单库单表解决方案
- 唯一索引 + 唯一字段
- 幂等
上面说的那种业务场景:sign_log 表会有 user_id、sign_id、sign_time 等。那么每次签到,每个人每天只有一条签到记录。
数据库层采取唯一索引的形式,保证数据记录唯一性。即 UNIQUE 约束,UNIQUE 约束唯一标识数据库表中的每条记录。另外,user_id,sign_id,sign_time 三个组合适唯一字段。创表的伪代码如下:
CREATE TABLE sign_log
(
id int NOT NULL,
user_id int NOT NULL,
sign_id int,
sign_time int,
CONSTRAINT unique_sign_log UNIQUE (user_id,sign_id,sign_time)
)
重点是 CONSTRAINT unique_sign_log UNIQUE (user_id,sign_id,sign_time)
。有个小问题,数据量大的时候,每条记录都会有对应的唯一索引,比较耗资源。那么这样就行了吗?
答案是不行,服务不够健壮。第一个请求插入成功,第二个请求直接报错,Java 服务会抛出 DuplicateKeyException
。
简单的幂等写法操作即可,伪代码如下:
class SignLogService {
public SingLogDO saveSignLog(SignLogDO log) {
// 幂等处理
SignLogDO insertLog = null;
try {
insertLog = signLogDAO.insert(log);
} catch (DuplicateKeyException e) {
insertLog = selectByUniqueKeys(userId,signId,signTime);
}
return insertLog;
}
}
的确,流量不是很大,也不算很高并发。重复写问题,这样处理即可。那大流量、高并发场景咋搞
2.分库分表解决方案
流量大了后,单库单表会演变成分库分表。那么基于单表的唯一索引形式,在碰到分表就无法保证呢,插入的地方可能是两个分表 A1 和 A2。
解决思路:将数据的唯一性条件放到其他存储,并进行锁控制
还是上面的例子,每天,每次签到,每个人只有一条签到记录。那么使用分布式锁 Redis 的解决方案。大致伪代码如下:
a.加锁
// 加锁
jedis.set(lockKey, requestId, "NX", "PX", expireTime);
- lockKey 最简单的是 user_id + sign_id + sign_time
- expireTime 设置为一天
b.解锁
// 解锁
jedis.eval(script, lockKey,requestId);
c.幂等代码加强
class SignLogService {
public SingLogDO saveSignLog(SignLogDO log) {
// 幂等校验
SignLogDO existLog = selectByUniqueKeys(userId,signId,signTime);
if(Objects.nonNull(existLog)) {
return existLog;
}
// 加锁
jedis.set
SignLogDO insertLog = signLogDAO.insert(log);
// 解锁
jedis.eval
return insertLog;
}
}
这个方案还是不是很成熟,大家参考下即可。
三、可落地小总结
解决方案实战中,了解具体术。归纳如下:
- 幂等:保证多次同意请求后结果一致
- 并发控制:单表唯一索引、分布式多表分布式锁
- 降级兜底方案:分布式锁锁失效 – 考虑乐观锁兜底
参考资料
- 重复插入方案: http://www.bysocket.com/archives/2266
- 《阿里巴巴 Java 开发手册》
以下专题教程也许您会有兴趣
![](http://www.bysocket.com/wp-content/uploads/2017/01/qrcode_for_gh_cd421e7eb7d6_430.jpg)
(关注微信公众号,领取 Java 精选干货学习资料)
相关文章
- 大数据治理平台应用解决方案
- Web3 的开发者,如何评估以及选择调用链上数据的解决方案
- 前端如何实现将多页数据合并导出到Excel单Sheet页解决方案|内附代码
- PCS 2022 | 腾讯多媒体实验室5篇论文入选,含视频压缩、视频数据集、神经网络压缩图像/视频压缩、高维媒体压缩等领域
- Visio 2016: 高效实用的数据可视化解决方案!+全版本安装包
- TRICONEX 3704E 数据配置和集成到交钥匙解决方案的接口
- MySQL中存储大量文本数据的解决方案(mysql长文本类型)
- 快速部署Oracle数据复制解决方案(oracle数据复制软件)
- 大数据存储解决方案:HDFS与MongoDB(hdfsmongodb)
- 恢复MySQL前一天的数据(mysql前一天的数据)
- MySQL无法插入中文数据解决方案(mysql无法插入中文)
- 处理MySQL中的二进制数据处理(mysql二进制数据)
- 解决方案:如何防止数据重复插入?
- 使用Oracle实现数据按天分区管理解决方案(oracle按天分区)
- 作为缓存Spark利用Redis缓冲数据的应用(spark需要redis)
- 大数据实现高效存储,Redis缓存解决方案(大数据缓存到redis)
- 利用Oracle主键实现可靠数据存储(oracle主键的使用)
- 收购无人机数据公司Redbird后,Airware引来Caterpillar集团投资、布局
- Magic Leap 回应曝光谍照:这不是AR产品原型机,只是用来收集数据
- 厉害了!数据科学杯:10000名数据科学家参加,清华博士斩获头奖 | GTC 2017
- 关于若干数据库数据插入性能的对比分析
- 解决js数据包含加号+通过ajax传到后台时出现连接错误