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

为了说明场景我们假设一个不太合理的场景 :
要更新某人的个人说明, 由于某种原因 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 已在评论进行了补充


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

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

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

《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
讨论数量: 18
UNI

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

1周前

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

1周前

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

1周前

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

1周前
leo

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

1周前

@leo 好的~感谢解答

1周前

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

1周前
LOST

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

1周前

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

1周前

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

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

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

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

表达能力欠佳,望谅解。

1周前

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

1周前
linxb

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

1周前
gangpula

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

1周前

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

1周前

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

1周前

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

1周前

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

1周前

  • 请注意单词拼写,以及中英文排版,参考此页
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
  • 支持表情,使用方法请见 Emoji 自动补全来咯,可用的 Emoji 请见 :metal: :point_right: Emoji 列表 :star: :sparkles:
  • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif
  • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
  请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!