情况是这样的,有些 api,第三方系统会推数据过来,有两个不同的 api,这两个 api 都会往一个表里面写同样的数据,两边业务逻辑上都有查重的,现在查历史记录,看见在有同时推数据到这两个接口的请求,导致写入重复的数据,创建时间都一样。除了加唯一索引有没有其他什么办法避免?
1
jugelizi 2019-06-29 10:29:58 +08:00 1
队列 加锁
|
2
haiyang416 2019-06-29 10:32:49 +08:00
写操作串行化,扔队列呗。
|
3
srx1982 2019-06-29 10:33:35 +08:00
"两边业务逻辑上都有查重的",都查重了,还能重,那就是查重写得不对
|
4
LeeSeoung 2019-06-29 10:34:00 +08:00
先查后写。。mysql 好像没 merge into
|
5
laravel 2019-06-29 10:40:10 +08:00
为啥把 unique 除了?
|
6
R18 2019-06-29 10:41:36 +08:00 via Android
查重没有加锁…
|
7
qiyuey 2019-06-29 10:49:14 +08:00
你没有开事务吧
|
11
xuanbg 2019-06-29 11:03:06 +08:00
数据库唯一索引外,唯一解决办法,API 上面加分布式锁
|
12
Vegetable 2019-06-29 11:11:11 +08:00
队列
API 将数据塞到队列里,顺序消费掉避免并发写入. 这牺牲了实时性.如果要求实时返回插入结果的话不适用,只能使用事务 |
13
chrisliu1314 2019-06-29 11:15:18 +08:00 via iPhone
1 )加个字段,然后加唯一索引
2 )或者这两个 api 都调用底层的一个服务接口。服务接口里面采用分布式锁+先查后插 不知道是否可行 |
14
zisway 2019-06-29 11:28:20 +08:00
数据库解决,pg 是有语法可以插入时遇到重复直接忽略,但不知道 mysql 有没有,可以查文档看看。业务上解决可以用分布式锁,或者 api 不入库,而是扔到 redis 队列中,由 job 去取出入库。
|
16
zy445566 2019-06-29 11:47:25 +08:00 via Android
如果没插入,开了事务还重复,九成是因为你没有判断每条 sql 是否有影响行数,就一个 try catch 就回滚了。事实上每条 sql 都要判断影响行数,如果影响行数为 0,理论上也是要进行回滚的。
|
18
limuyan44 2019-06-29 12:00:18 +08:00 via Android
唯一索引建不了你怎么判断重复的,按你说如果重复的数据你都分不清哪一条有没有用,那你去重的意义在哪里?
|
19
harvies 2019-06-29 12:16:40 +08:00
15 楼正解
|
20
sunjiayao 2019-06-29 12:17:56 +08:00
看着是有历史遗留问题,导致存量数据有重复数据。而且不好清洗。如果对数据及时性要求不高,我觉得可以 copy 一张一模一样的表,并添加唯一索引。使用 duplicate key update 插入数据。然后定时 insert select 同步表数据。相对来说插入效率和开发难度比自己加锁要快吧。
|
21
Navee 2019-06-29 12:22:00 +08:00
何必为难自己
|
22
yiplee 2019-06-29 13:06:07 +08:00
建一新张表它的主键当唯一索引用。插入的时候开启事务,先插入新表,如果插入成功了,再插入现有的业务表。
|
23
qf19910623 2019-06-29 13:16:32 +08:00
做一个短时间的缓存锁队列
|
24
agui2200 2019-06-29 14:21:59 +08:00
用 for update + 事务,查询共有的全局锁表,做悲观锁
|
27
lihongjie0209 2019-06-29 14:50:12 +08:00
只能用队列了
|
28
Kylinsun 2019-06-29 15:20:41 +08:00 via iPhone
建议增加一个字段,然后需要加唯一键的列加强这个辅助唯一键作为唯一键。
|
29
hosaos 2019-06-29 15:53:20 +08:00
针对你该条数据的业务唯一建做分布式锁,抢锁成功后先查后插入
|
30
karllynn 2019-06-29 15:57:44 +08:00
开分布式锁或者串行化
或者你新建一张表 /加个字段,然后把原来的数据处理一下迁移过来 |
31
Cbdy 2019-06-29 16:07:34 +08:00
把其中一个 API 的请求代理另一个 API
|
32
linbiaye 2019-06-29 16:45:49 +08:00
笨方法:分布式锁,可以基于数据库做,不需要引入其它组件。
begin(read committed 级别即可) 1. 插入锁表 2. 根据待插入数据 count 是否已存在,存在则 rollback 4. 插入数据 5. 删除锁 commit 个人倾向的方法:表新加个唯一 column |
33
passerbytiny 2019-06-29 18:05:34 +08:00
悲观锁方式:查重的时候直接锁表(因为后面是要新增数据的,所以只能锁表),新增数据或超时后解锁。此方式基本没人用。
变相乐观锁方式:第三方直接推送,若收到“有重复数据”错误再做后续处理;你这边单事务内查重加插入。此方式没啥特殊性,就是注册用户判断重复的逻辑,但是若你这个业务是高并发并且冲突情况占比大,此方式也不是太合适。 如果是高并发场景,并且第三方确实会发送重复数据,建议还是允许重复数据的好。或者,给第三方分配 ID,表中加一列“来源”,这样就不会出现重复数据了。 你的描述少了一个关键场景:第三方查重后如果发现重复了,是怎么处理的。 |
34
Takamine 2019-06-29 18:54:36 +08:00 via Android
得看具体业务,乐观一点或者悲观一点。
|
35
jaskle 2019-06-30 07:03:51 +08:00 via Android
你无法保证外部数据是否有重复,就算你有队列和分布式锁,但是他就是发了两个一样的,所以加唯一索引是最佳选择,如果旧数据过多可以考虑双联合索引,手动差异化一下。
|
36
msg7086 2019-07-01 03:35:50 +08:00
要避免同时写入那就只能串行化。串行化要么加锁,要么队列。
|
37
qsbaq 2019-07-01 09:42:38 +08:00
唯一索引是最佳选择。
|
38
justRua 2019-07-01 11:48:15 +08:00
这是发生幻读了吧,把数据库隔离级别设置成串行化,也可以用队列在业务层串行化,唯一索引貌似是最方便的。
|
39
werty 2019-07-01 11:52:47 +08:00
写之前全表加锁, 然后再 select 一次, 看看有没有主键重复, 最后 insert;
只需要改改 DAO 层就够了 |
40
IamUNICODE 2019-07-01 13:04:57 +08:00
用 redis 的话,setnx 一下?
|
41
dyllen OP @passerbytiny 其实这个也算不上什么高并发,就是第三方会完全不分青红皂白,同时请求了两个会写相同数据的不同接口。
|
42
dyllen OP @passerbytiny 重复了要把重复的删掉,反馈之后我查了一下,数据不多,有个 20 条左右相同的数据的吧,因为都还在部分测试吧。
|
43
javaWeber 2019-07-01 14:11:34 +08:00
select for update,排他锁。先查再写入。
|