[疑问] [已解决] updateOrCreate () 这类方法应对并发请求的问题
为了说明场景我们假设一个不太合理的场景 :
要更新某人的个人说明, 由于某种原因 user_id 在 Message 表中并不是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) 已在评论进行了补充
已解决:
如果把问题扩展到提交的用户不仅是同一个人的情况, 那么问题可以归类为 并发下重复写入 的问题,
而一般的解决方式有以下几种:
- 加
unique索引 - 加
表锁 - 缓存过滤重复提交
更多解决方式可搜索 如何并发下避免重复写入
问题参考
本作品采用《CC 协议》,转载必须注明作者和本文链接
关于 LearnKu
性能要求不高的话 Mysql transction即可
@UNI 由于每条sql语句的执行结果都是成功的, 我想事务并不能解决问题. 我觉得这是laravel层面的问题
前端JS控制或者后端加锁都行 搞不懂把这个问题甩在laravel上?
@Rekkles 我没有在埋怨框架的意思, laravel十分优秀. 我是一名刚从事网站开发的应届生, 对于很多方面的没有经验. 对于什么时候该依赖框架的SQL查询而不用考虑数据库的表锁问题. 什么时候应该自己主动考虑这个问题之间的界限不是很清楚.
之前没有实际碰到需要对数据库加锁的问题, 这是我一次遇见. 如果这条请求不是只有一个用户对该条件的sql进行更新, 那是否意味着只有表锁能解决这个问题了. 如果是的话, 我很高兴我遇到了这个问题. 他会让我有兴趣去进步一了解"锁"问题. Thanks for your help.
你的理解是对的,这个方法不适合在有并发的时候使用
@leo 好的~感谢解答
头一回见到提出的问题有一定技术深度又表述清楚的贴,感人。
其实这不单单是
updateOrCreate这个方法的问题,本质还是如何避免重复提交导致重复写入。@LOST 你为我提供了很好的关键词! "如何避免重复提交导致重复写入" :smiley:
其实我刚刚认真思考了这个问题,我不知道理解的对不对。你是想知道在 Laravel 层面,或者说
updateOrCreate是否有方法规避这个问题,如果没有,那是不是不够 "严谨",需要在某些场景避免。我觉得这个问题似乎问得... 很巧妙。
因为从原生 SQL,或者说,从整个生产环境的系统来看,有无数种方式可以避免这种情况,就像前面说的一些关键词,可以搜索到不少在生产环境下经过验证的方法。
但是就单纯看 Laravel 的
updateOrCreate,似乎的确没有什么可以在没有 「Unique 索引」、「可承担高并发」又「不借助其他工具」的解决方案。表达能力欠佳,望谅解。
我认为这和高并发没有关系
执行updateOrCreate之前使用事务锁lock,这样就能避免重复插入的问题了
@tookit 嗯,并发是来源于不同用户,这个只是单用户连续非必要点击,可以前端控制的,然后保险点,laravel这个方法在这种情况就别用,以防万一,完事
其实我觉得问题主要原因是可以让用户1秒内能请求多次接口,前端只要在用户点击之后弹出 loading 窗,禁用了点击事件,就能解决这个问题,但如果是用户直接请求 api,这种非正规的情况,可以通过限制接口请求频率的方式来解决。
@Wi1dcard 理解非常非常到位! 看来这种
并发下避免重复写入的问题的确需要我们自己去考虑, 而不应该依赖框架来@LiuKaHo 这是针对
同一用户请求的情况下, 将重复请求拦截的很好的解决方案.@Hans941 如果针对高并发,可以通过锁机制来解决的,或者通过把数据先存到缓存,再通过同步服务把数据更新到数据库。
@LiuKaHo 正解
这确实是 Laravel 框架本身应该考虑的问题,这个问题在 Laravel 的 Github 里讨论非常激烈。很多人认为应该在应用层去解决,不应该甩锅给框架,但是试想一下如果所有问题都在应用层去处理,那还要框架干嘛?而且 Laravel 不就是要成为一个优雅的框架吗?
updateOrCreate 之类的两次查询类方法在高并发的情况下都会碰到问题,当然可以用 transction 或 lock 去解决。不过我认为应该透过现象看本质,表面上看是并发问题,其实是性能和数据库层面的问题。很多程序员就质疑了这类方法的性能问题: UpdateOrCreate -> Performance。现在很多数据库都支持 upsert,Laravel 只要尽快兼容 upsert 功能,就能从根本解决这类问题。两次查询变成一次查询,不用担心并发冲突问题,不用事务不用加锁。我们程序员们只要关心业务层就好了。要知道,越是底层解决方案效果越好,用业务逻辑去解决问题最复杂、成本最高、效果最差。
报错,用这个方法,表设置一个唯一索引的。然后插入的时候报错了,唯一索引冲突。 看了下,一秒内,请求三次,就报错。