V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
wmhack
V2EX  ›  问与答

insert 重复插入问题

  •  
  •   wmhack · 2019-01-31 12:49:44 +08:00 · 3355 次点击
    这是一个创建于 2121 天前的主题,其中的信息可能已经有所发展或是发生改变。

    先写点伪代码:

    select * from user where email = [email protected] and sex = 1;

    if ( rs = null ){ insert ………… }

    这种写法在单机单线程的情况不会出问题,可生产是两台机器。

    有一次刚好碰到了两台机器同是 select,结果为空,就同时插入了,

    导致后来出现了两条记录。

    问一下各位 V 友,这种情况要怎样解决呢?

    36 条回复    2019-02-02 01:33:04 +08:00
    nybux
        1
    nybux  
       2019-01-31 13:01:49 +08:00
    1.select for update
    2.用唯一
    3.为啥邮箱和性别要唯一?
    id4alex
        2
    id4alex  
       2019-01-31 13:03:24 +08:00
    上事务
    milesnihao
        3
    milesnihao  
       2019-01-31 13:05:24 +08:00
    数据库事务和锁?
    GDC
        4
    GDC  
       2019-01-31 13:06:02 +08:00 via iPhone   ❤️ 1
    加唯一键
    itskingname
        5
    itskingname  
       2019-01-31 13:06:43 +08:00
    上分布式锁。
    wmhack
        6
    wmhack  
    OP
       2019-01-31 13:23:44 +08:00
    @nybux 这里不是 update 操作啊
    wmhack
        7
    wmhack  
    OP
       2019-01-31 13:25:04 +08:00
    @GDC 目前我们是做了唯一索引,还有方法加了 sychnazed,但这不是最优方案啊。
    liprais
        8
    liprais  
       2019-01-31 13:25:14 +08:00
    你的隔离级别设的是啥
    wmhack
        9
    wmhack  
    OP
       2019-01-31 13:26:56 +08:00
    @nybux 这里只是举这么一个例子。实际情况,不是这两个条件。
    wmhack
        10
    wmhack  
    OP
       2019-01-31 13:28:48 +08:00
    @liprais 感觉跟隔离级别没有太大关系,主要是两台机器同时 select 了一样的结果出来
    RoyL
        11
    RoyL  
       2019-01-31 13:29:47 +08:00
    @wmhack
    ....for update 是锁,看来你一点不懂 sql
    ixiaozhi
        12
    ixiaozhi  
       2019-01-31 13:30:42 +08:00
    1. 加个 requestid 从前端到后端做锁
    2. 几个唯一条件联合做锁,如 redis
    wmhack
        13
    wmhack  
    OP
       2019-01-31 13:33:06 +08:00
    @id4alex 事物也控制不了同时 select 呀
    id4alex
        14
    id4alex  
       2019-01-31 13:41:45 +08:00
    @wmhack

    上事务 加上 insert tablename select 'col1' as col1, 'col2' as col2 from 伪表 where not exist select 1 from tablename where your condition
    GoLand
        15
    GoLand  
       2019-01-31 13:44:16 +08:00
    这加个 unique key 就能解决的吧。
    id4alex
        16
    id4alex  
       2019-01-31 13:44:59 +08:00
    前面也不用判断 RS == null 了, 直接执行这个 SQL 就可以了
    gejun123456
        17
    gejun123456  
       2019-01-31 13:46:57 +08:00 via iPhone
    加了唯一索引 就不会两条都插入了
    haoz1w0w
        18
    haoz1w0w  
       2019-01-31 14:16:53 +08:00
    唯一索引
    ThirdFlame
        19
    ThirdFlame  
       2019-01-31 14:30:32 +08:00
    唯一所以 可以避免两次插入,第一次插入成功了,第二次就出错了。 也就无需 select 了,直接干
    wmhack
        20
    wmhack  
    OP
       2019-01-31 14:51:13 +08:00
    @ThirdFlame 是的,这是第二重限制。但报错的方式有时也不太好,如果没有更好的方案,那就只能靠唯一索引去控制了
    wmhack
        21
    wmhack  
    OP
       2019-01-31 14:57:16 +08:00
    @id4alex 我们是 oracle 数据库,类似这种 insert 时 where 的语句当时试过,mybatis 会报错,所以当时就放弃了
    xomix
        22
    xomix  
       2019-01-31 16:05:18 +08:00   ❤️ 1
    有很多解决方案,根据你实际项目资源和项目阶段选择:
    1、上唯一索引,只要有 1 条完成插入后第二条再次插入就会报错。
    2、利用 rides、zookeeper 等第三方分布式 k-v 存储制造分布式锁,利用分布式锁完成插入。
    3、将所有写操作集中到同一进程,入列后等待完成。

    最简单最便宜的是 1,但是这样你要拦截错误后自行处理,代码逻辑复杂度升高。
    接下来是 3,但是如果采用方案 3 当你的写入队列进程挂掉或阻塞的时候会引起服务不可用。
    2 是时下主流解决方案,但是投入资本最高,开发难度也较高。
    ixiaozhi
        23
    ixiaozhi  
       2019-01-31 16:32:19 +08:00
    想到个奇怪的思路,大佬们评估下可行性
    1. 正常插入,插 2 条就 2 条吧
    2. 查询的时候,控制以 id 小的为准 order by id limit 1
    3. (可选)定期洗理重复且 id 大的数据
    leon0903
        24
    leon0903  
       2019-01-31 16:53:35 +08:00
    设置好唯一索引,然后插入语句改为 insert into on duplicate update,这样即使数据重复插入也只是更新而已。
    kkkkkrua
        25
    kkkkkrua  
       2019-01-31 17:08:50 +08:00
    不想在数据库做处理就用分布式锁,或者吧这个 insert 动作丢给队列。一个一个处理
    dilu
        26
    dilu  
       2019-01-31 17:52:27 +08:00   ❤️ 1
    1. 插入前锁全表 开发成本最低,但是数据库开销最大并且有可能会影响业务 如果业务无所谓这种方法最合适
    2. 设置一个唯一索引 开发成本低但是数据库开销大,如果本来就有一个字段需要加索引这种方法最合适
    3. 用 redis 等缓存做分布式锁,拿到锁的进程 insert 别的进程返回。开发成本适中,反正分布式锁你后你也会用的。
    4. 消息队列,直接把 insert 操作扔进去 消费者来做就行了。但是开发成本很高了。
    5. 分布式事务 这种成本最高 了解一下就 OK
    bk201
        27
    bk201  
       2019-01-31 18:26:55 +08:00
    幻读,事务调到 SERIALIZABLE 级别
    petelin
        28
    petelin  
       2019-01-31 20:08:26 +08:00 via iPhone
    都跟你说了 select for update。不存在会锁住的
    mmdsun
        29
    mmdsun  
       2019-01-31 20:16:08 +08:00 via Android
    select lock in share mode 就行了吧。
    wmhack
        30
    wmhack  
    OP
       2019-01-31 20:42:57 +08:00 via iPhone
    @xomix 这些方案感觉可行的,很好的一些建议
    wmhack
        31
    wmhack  
    OP
       2019-01-31 20:43:30 +08:00 via iPhone
    @dilu 这些思路很好,感谢了🙏
    wmhack
        32
    wmhack  
    OP
       2019-01-31 20:45:28 +08:00 via iPhone
    哪位大神如果有更好的方案,欢迎提出来额。大伙表示感谢啦
    kaid97
        33
    kaid97  
       2019-01-31 22:13:07 +08:00
    在同个事务下,select for update 会用 next-key lock 锁住索引不让插入
    zhenjiachen
        34
    zhenjiachen  
       2019-01-31 22:32:42 +08:00 via iPhone
    重复提交问题,把请求的参数 md5 后存到 redis,然后设置一个超时时间,第二次来了从 redis 中先判断是否有现在的 md5 值有就判断为重复提交,没有就通过
    Leigg
        35
    Leigg  
       2019-01-31 23:49:11 +08:00 via iPhone
    思路都差不多,在客户端和数据库中间加一层中间件做控制。锁的实现最好避免在数据库端实现,利用 redis/MQ 实现,实现高效的锁。
    akira
        36
    akira  
       2019-02-02 01:33:04 +08:00
    INSERT IGNORE . 直接忽略后面的插入操作。 但是这种操作不是很严谨,个人还是比较倾向于使用队列
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1008 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 20:04 · PVG 04:04 · LAX 12:04 · JFK 15:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.