V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
MySQL 5.5 Community Server
MySQL 5.6 Community Server
Percona Configuration Wizard
XtraBackup 搭建主从复制
Great Sites on MySQL
Percona
MySQL Performance Blog
Severalnines
推荐管理工具
Sequel Pro
phpMyAdmin
推荐书目
MySQL Cookbook
MySQL 相关项目
MariaDB
Drizzle
参考文档
http://mysql-python.sourceforge.net/MySQLdb.html
dyllen
V2EX  ›  MySQL

msyql 除了加唯一索引,又其他办法防止同一时刻写入重复数据么?

  •  
  •   dyllen · 2019-06-29 10:27:40 +08:00 · 8984 次点击
    这是一个创建于 2007 天前的主题,其中的信息可能已经有所发展或是发生改变。

    情况是这样的,有些 api,第三方系统会推数据过来,有两个不同的 api,这两个 api 都会往一个表里面写同样的数据,两边业务逻辑上都有查重的,现在查历史记录,看见在有同时推数据到这两个接口的请求,导致写入重复的数据,创建时间都一样。除了加唯一索引有没有其他什么办法避免?

    第 1 条附言  ·  2019-06-29 11:01:14 +08:00
    唯一索引加不了了,里面有重复的不知道有没有用的数据了
    43 条回复    2019-07-01 14:11:34 +08:00
    jugelizi
        1
    jugelizi  
       2019-06-29 10:29:58 +08:00   ❤️ 1
    队列 加锁
    haiyang416
        2
    haiyang416  
       2019-06-29 10:32:49 +08:00
    写操作串行化,扔队列呗。
    srx1982
        3
    srx1982  
       2019-06-29 10:33:35 +08:00
    "两边业务逻辑上都有查重的",都查重了,还能重,那就是查重写得不对
    LeeSeoung
        4
    LeeSeoung  
       2019-06-29 10:34:00 +08:00
    先查后写。。mysql 好像没 merge into
    laravel
        5
    laravel  
       2019-06-29 10:40:10 +08:00
    为啥把 unique 除了?
    R18
        6
    R18  
       2019-06-29 10:41:36 +08:00 via Android
    查重没有加锁…
    qiyuey
        7
    qiyuey  
       2019-06-29 10:49:14 +08:00
    你没有开事务吧
    dyllen
        8
    dyllen  
    OP
       2019-06-29 10:57:54 +08:00
    @laravel 不知道,原来就没有加,现在加不了了,里面有不少重复的也不知道有没有用的数据
    dyllen
        9
    dyllen  
    OP
       2019-06-29 10:58:41 +08:00
    @qiyuey 开了,两边是独立的。
    dyllen
        10
    dyllen  
    OP
       2019-06-29 10:59:53 +08:00
    @R18 查到没有,我锁哪里,我锁表?
    xuanbg
        11
    xuanbg  
       2019-06-29 11:03:06 +08:00
    数据库唯一索引外,唯一解决办法,API 上面加分布式锁
    Vegetable
        12
    Vegetable  
       2019-06-29 11:11:11 +08:00
    队列
    API 将数据塞到队列里,顺序消费掉避免并发写入.
    这牺牲了实时性.如果要求实时返回插入结果的话不适用,只能使用事务
    chrisliu1314
        13
    chrisliu1314  
       2019-06-29 11:15:18 +08:00 via iPhone
    1 )加个字段,然后加唯一索引
    2 )或者这两个 api 都调用底层的一个服务接口。服务接口里面采用分布式锁+先查后插
    不知道是否可行
    zisway
        14
    zisway  
       2019-06-29 11:28:20 +08:00
    数据库解决,pg 是有语法可以插入时遇到重复直接忽略,但不知道 mysql 有没有,可以查文档看看。业务上解决可以用分布式锁,或者 api 不入库,而是扔到 redis 队列中,由 job 去取出入库。
    799635347
        15
    799635347  
       2019-06-29 11:36:25 +08:00 via iPhone
    1.分布式锁
    2.mq
    zy445566
        16
    zy445566  
       2019-06-29 11:47:25 +08:00 via Android
    如果没插入,开了事务还重复,九成是因为你没有判断每条 sql 是否有影响行数,就一个 try catch 就回滚了。事实上每条 sql 都要判断影响行数,如果影响行数为 0,理论上也是要进行回滚的。
    zy445566
        17
    zy445566  
       2019-06-29 11:48:23 +08:00 via Android
    @zy445566 如果有插入,就开串行化级别的事务,但这个特别影响性能。
    limuyan44
        18
    limuyan44  
       2019-06-29 12:00:18 +08:00 via Android
    唯一索引建不了你怎么判断重复的,按你说如果重复的数据你都分不清哪一条有没有用,那你去重的意义在哪里?
    harvies
        19
    harvies  
       2019-06-29 12:16:40 +08:00
    15 楼正解
    sunjiayao
        20
    sunjiayao  
       2019-06-29 12:17:56 +08:00
    看着是有历史遗留问题,导致存量数据有重复数据。而且不好清洗。如果对数据及时性要求不高,我觉得可以 copy 一张一模一样的表,并添加唯一索引。使用 duplicate key update 插入数据。然后定时 insert select 同步表数据。相对来说插入效率和开发难度比自己加锁要快吧。
    Navee
        21
    Navee  
       2019-06-29 12:22:00 +08:00
    何必为难自己
    yiplee
        22
    yiplee  
       2019-06-29 13:06:07 +08:00
    建一新张表它的主键当唯一索引用。插入的时候开启事务,先插入新表,如果插入成功了,再插入现有的业务表。
    qf19910623
        23
    qf19910623  
       2019-06-29 13:16:32 +08:00
    做一个短时间的缓存锁队列
    agui2200
        24
    agui2200  
       2019-06-29 14:21:59 +08:00
    用 for update + 事务,查询共有的全局锁表,做悲观锁
    agui2200
        25
    agui2200  
       2019-06-29 14:22:20 +08:00
    @agui2200 这个方案实施起来比较简单,但是性能差一些
    Asice
        26
    Asice  
       2019-06-29 14:36:20 +08:00
    @agui2200 #24 已经是高并发才会出现重复了,还悲观锁,要搞死数据库吗
    lihongjie0209
        27
    lihongjie0209  
       2019-06-29 14:50:12 +08:00
    只能用队列了
    Kylinsun
        28
    Kylinsun  
       2019-06-29 15:20:41 +08:00 via iPhone
    建议增加一个字段,然后需要加唯一键的列加强这个辅助唯一键作为唯一键。
    hosaos
        29
    hosaos  
       2019-06-29 15:53:20 +08:00
    针对你该条数据的业务唯一建做分布式锁,抢锁成功后先查后插入
    karllynn
        30
    karllynn  
       2019-06-29 15:57:44 +08:00
    开分布式锁或者串行化

    或者你新建一张表 /加个字段,然后把原来的数据处理一下迁移过来
    Cbdy
        31
    Cbdy  
       2019-06-29 16:07:34 +08:00
    把其中一个 API 的请求代理另一个 API
    linbiaye
        32
    linbiaye  
       2019-06-29 16:45:49 +08:00
    笨方法:分布式锁,可以基于数据库做,不需要引入其它组件。
    begin(read committed 级别即可)
    1. 插入锁表
    2. 根据待插入数据 count 是否已存在,存在则 rollback
    4. 插入数据
    5. 删除锁
    commit
    个人倾向的方法:表新加个唯一 column
    passerbytiny
        33
    passerbytiny  
       2019-06-29 18:05:34 +08:00
    悲观锁方式:查重的时候直接锁表(因为后面是要新增数据的,所以只能锁表),新增数据或超时后解锁。此方式基本没人用。
    变相乐观锁方式:第三方直接推送,若收到“有重复数据”错误再做后续处理;你这边单事务内查重加插入。此方式没啥特殊性,就是注册用户判断重复的逻辑,但是若你这个业务是高并发并且冲突情况占比大,此方式也不是太合适。

    如果是高并发场景,并且第三方确实会发送重复数据,建议还是允许重复数据的好。或者,给第三方分配 ID,表中加一列“来源”,这样就不会出现重复数据了。

    你的描述少了一个关键场景:第三方查重后如果发现重复了,是怎么处理的。
    Takamine
        34
    Takamine  
       2019-06-29 18:54:36 +08:00 via Android
    得看具体业务,乐观一点或者悲观一点。
    jaskle
        35
    jaskle  
       2019-06-30 07:03:51 +08:00 via Android
    你无法保证外部数据是否有重复,就算你有队列和分布式锁,但是他就是发了两个一样的,所以加唯一索引是最佳选择,如果旧数据过多可以考虑双联合索引,手动差异化一下。
    msg7086
        36
    msg7086  
       2019-07-01 03:35:50 +08:00
    要避免同时写入那就只能串行化。串行化要么加锁,要么队列。
    qsbaq
        37
    qsbaq  
       2019-07-01 09:42:38 +08:00
    唯一索引是最佳选择。
    justRua
        38
    justRua  
       2019-07-01 11:48:15 +08:00
    这是发生幻读了吧,把数据库隔离级别设置成串行化,也可以用队列在业务层串行化,唯一索引貌似是最方便的。
    werty
        39
    werty  
       2019-07-01 11:52:47 +08:00
    写之前全表加锁, 然后再 select 一次, 看看有没有主键重复, 最后 insert;
    只需要改改 DAO 层就够了
    IamUNICODE
        40
    IamUNICODE  
       2019-07-01 13:04:57 +08:00
    用 redis 的话,setnx 一下?
    dyllen
        41
    dyllen  
    OP
       2019-07-01 13:43:42 +08:00
    @passerbytiny 其实这个也算不上什么高并发,就是第三方会完全不分青红皂白,同时请求了两个会写相同数据的不同接口。
    dyllen
        42
    dyllen  
    OP
       2019-07-01 13:46:38 +08:00
    @passerbytiny 重复了要把重复的删掉,反馈之后我查了一下,数据不多,有个 20 条左右相同的数据的吧,因为都还在部分测试吧。
    javaWeber
        43
    javaWeber  
       2019-07-01 14:11:34 +08:00
    select for update,排他锁。先查再写入。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1051 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 19:50 · PVG 03:50 · LAX 11:50 · JFK 14:50
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.