[疑问] [已解决] updateOrCreate () 这类方法应对并发请求的问题

为了说明场景我们假设一个不太合理的场景 :
要更新某人的个人说明, 由于某种原因 user_idMessage 表中并不是unique的, 处理如下

  Message::updateOrCreate(['user_id' => $user_id],  ['content' => $content, 'age' => $age]);

现在有一个新用户, 他进入到了个人信息页面要补充自己的个人信息, 填写完 content 后点击 submit 发送 'ajax' 请求, 但他1s内点击了多次, 就会导致数据库出现多条同一 user_id 的多条记录.
这显然不是我们想要的结果, 我们预期每个 user_id 只有一条记录.
产生这个问题的原因是在 updateOrCreate() 方法会先根据条件 user_id 进行查询, 而在连续的快速点击发送的 ajax 请求中数据库会在执行

insert into messages ..... 

操作前执行多条

select * from messages where (user_id = 2) limit 1

查询, 而查询结果都会显示为空(因为 insert 还未被执行), updateOrCreate 判断需要的是都是 create操作而不是 update, 结果数据库就会产生多条同一 'user_id' 同一 'content'的结果.

这种每秒不超过5次的请求都会产生意料之外的结果, 这是否意味着 updateOrCreate 这类由框架封装的查询语句不适合在某些严谨的应用场景下使用?

以上假设已经验证过成立, 可惜不能发动图...没法发上来
也许这个问题有些不合理, 甚至很愚蠢.
如果您能指出或者解答该问题, 这将对我帮助很大!
谢谢!

问题表述不够严谨的: [[[[[@Wi1dcard](https://learnku.com/users/32249)](https://learnku.com/users/32249)](https://learnku.com/users/32249)](https://learnku.com/users/32249)](https://learnku.com/users/32249) 已在评论进行了补充


已解决:
如果把问题扩展到提交的用户不仅是同一个人的情况, 那么问题可以归类为 并发下重复写入 的问题,
而一般的解决方式有以下几种:

  1. unique 索引
  2. 表锁
  3. 缓存过滤重复提交

更多解决方式可搜索 如何并发下避免重复写入
问题参考

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 20

这确实是 Laravel 框架本身应该考虑的问题,这个问题在 Laravel 的 Github 里讨论非常激烈。很多人认为应该在应用层去解决,不应该甩锅给框架,但是试想一下如果所有问题都在应用层去处理,那还要框架干嘛?而且 Laravel 不就是要成为一个优雅的框架吗?

updateOrCreate 之类的两次查询类方法在高并发的情况下都会碰到问题,当然可以用 transction 或 lock 去解决。不过我认为应该透过现象看本质,表面上看是并发问题,其实是性能和数据库层面的问题。很多程序员就质疑了这类方法的性能问题: UpdateOrCreate -> Performance。现在很多数据库都支持 upsert,Laravel 只要尽快兼容 upsert 功能,就能从根本解决这类问题。两次查询变成一次查询,不用担心并发冲突问题,不用事务不用加锁。我们程序员们只要关心业务层就好了。要知道,越是底层解决方案效果越好,用业务逻辑去解决问题最复杂、成本最高、效果最差。

3年前 评论
leo

你的理解是对的,这个方法不适合在有并发的时候使用

5年前 评论

其实我刚刚认真思考了这个问题,我不知道理解的对不对。你是想知道在 Laravel 层面,或者说 updateOrCreate 是否有方法规避这个问题,如果没有,那是不是不够 "严谨",需要在某些场景避免。

我觉得这个问题似乎问得... 很巧妙。

因为从原生 SQL,或者说,从整个生产环境的系统来看,有无数种方式可以避免这种情况,就像前面说的一些关键词,可以搜索到不少在生产环境下经过验证的方法。

但是就单纯看 Laravel 的 updateOrCreate,似乎的确没有什么可以在没有 「Unique 索引」、「可承担高并发」又「不借助其他工具」的解决方案。

表达能力欠佳,望谅解。

5年前 评论

其实我觉得问题主要原因是可以让用户1秒内能请求多次接口,前端只要在用户点击之后弹出 loading 窗,禁用了点击事件,就能解决这个问题,但如果是用户直接请求 api,这种非正规的情况,可以通过限制接口请求频率的方式来解决。

5年前 评论

头一回见到提出的问题有一定技术深度又表述清楚的贴,感人。

5年前 评论
LOST

其实这不单单是 updateOrCreate 这个方法的问题,本质还是如何避免重复提交导致重复写入。

5年前 评论

@LOST 你为我提供了很好的关键词! "如何避免重复提交导致重复写入" :smiley:

5年前 评论

我认为这和高并发没有关系

5年前 评论

报错,用这个方法,表设置一个唯一索引的。然后插入的时候报错了,唯一索引冲突。 看了下,一秒内,请求三次,就报错。

2年前 评论

@Hans941 如果针对高并发,可以通过锁机制来解决的,或者通过把数据先存到缓存,再通过同步服务把数据更新到数据库。

5年前 评论

@LiuKaHo 这是针对 同一用户 请求的情况下, 将 重复请求拦截 的很好的解决方案.

5年前 评论

@Wi1dcard 理解非常非常到位! 看来这种 并发避免重复写入 的问题的确需要我们自己去考虑, 而不应该依赖框架来

5年前 评论

@tookit 嗯,并发是来源于不同用户,这个只是单用户连续非必要点击,可以前端控制的,然后保险点,laravel这个方法在这种情况就别用,以防万一,完事

5年前 评论

执行updateOrCreate之前使用事务锁lock,这样就能避免重复插入的问题了

5年前 评论

性能要求不高的话 Mysql transction即可

5年前 评论

@Rekkles 我没有在埋怨框架的意思, laravel十分优秀. 我是一名刚从事网站开发的应届生, 对于很多方面的没有经验. 对于什么时候该依赖框架的SQL查询而不用考虑数据库的表锁问题. 什么时候应该自己主动考虑这个问题之间的界限不是很清楚.
之前没有实际碰到需要对数据库加锁的问题, 这是我一次遇见. 如果这条请求不是只有一个用户对该条件的sql进行更新, 那是否意味着只有表锁能解决这个问题了. 如果是的话, 我很高兴我遇到了这个问题. 他会让我有兴趣去进步一了解"锁"问题. Thanks for your help.

5年前 评论

前端JS控制或者后端加锁都行 搞不懂把这个问题甩在laravel上?

5年前 评论

@UNI 由于每条sql语句的执行结果都是成功的, 我想事务并不能解决问题. 我觉得这是laravel层面的问题

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!