[疑问] [已解决] 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 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 20

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

5年前 评论

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

5年前 评论

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

5年前 评论

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

5年前 评论
leo

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

5年前 评论

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

5年前 评论
LOST

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

5年前 评论

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

5年前 评论

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

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

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

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

表达能力欠佳,望谅解。

5年前 评论

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

5年前 评论

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

5年前 评论

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

5年前 评论

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

5年前 评论

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

5年前 评论

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

5年前 评论

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

5年前 评论

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

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

3年前 评论

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

2年前 评论

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