并发数据库调用的PHP / Mysql问题

时间:2019-03-05 18:42:51

标签: php mysql database

在PHP中使用innoDB表和mysqli包装器进行查询。

我们当前遇到一个问题,就是每秒请求1500次相同脚本的流量激增。

一种情况是,访问脚本的前X个用户获得了奖励。

奖品是“奖品”表上的一条记录,该表中有#个已声明的编号和#个已分配的编号。

一旦使用的金额> =分配的金额,我们将停止奖励。

正在发生的事情是,在脚本的其他实例可以更新该行之前,对该脚本的许多请求正在同时读取该行,从而向他们显示仍然还有#个奖品需要领取。这导致我们奖励的金额超过了所定金额。

关于如何规避此事的任何想法?

3 个答案:

答案 0 :(得分:1)

对,您描述的是经典比赛条件。

一种解决方案是在更新之前使用SELECT...FOR UPDATE在奖品表中的行上建立锁。 InnoDB将建立对锁的请求顺序,使每个请求等待其轮换,直到它可以获取锁为止。

但是,这不是一个好的解决方案,因为它将导致每个用户的浏览器旋转并旋转,等待响应。在服务器上,您将迅速获得每秒1500个锁定请求的排队。即使每个会话仅花费10毫秒来完成SELECT FOR UPDATE和随后的UPDATE(这已经非常雄心勃勃),每秒钟要做的工作仍然是15.0秒。到第二秒,您将有30.0秒的工作要做。

与此同时,用户正在查看浏览器的挂起状态,直到转身过来。这几乎是一项设计的突破。

基本上,您需要一些解决方案来建立请求的顺序,即:

  • 全球
  • 原子
  • 快速
  • 异步

您可以让每个并发请求使用AUTO_INCREMENT键对表进行INSERT,这将保证其顺序。然后,一旦有X行,随后的请求就不会再插入任何行了。

另一种方法是使用消息队列。每个请求只是将自己的请求推入队列。然后,一个消费者从队列中拉出前X个请求,并向他们奖励。队列中的其余请求将被转储,并且没有获得奖励。

答案 1 :(得分:1)

虽然您不提供代码,表结构或更深入的数据库信息,但是在这些情况下的首要建议是使用LOCK

如果表是innoDB,则可以从行级锁定中受益,尽管如果表只有一行,则无关紧要。

使用伪代码,您需要在每次命中时

LOCK TABLE prizes
SELECT claimed, alloted FROM prizes

if claimed < alloted award prize
UPDATE prizes set claimed = claimed +1

else
do_nothing
UNLOCK TABLE prizes

<<after unlocking>>
if the user got an award, do whatever you need to do to award the prize which is not "inventory-sensitive" and can be done asynchronously

此操作的运行时间将以毫秒为单位,因此,如果您的数据库服务器涂满油脂,则所有命中都排队就不会成为问题,尽管您可能会遇到应用程序的进程限制或连接限制服务器,因此需要进行一些压力测试。

这可能是棘手的,复杂的,挑剔的...

更简单的方法是:

设置一个claim_attempt表,其中包含一个自动递增的主键和一个引用有关用户的字段。

在每个匹配项上,插入一条记录(无论可用库存如何)并检索插入的行的ID。 然后,将检索到的ID与分配的奖励号进行比较。如果id <= alloted,则运行任何需要运行的过程才能将奖品提供给用户。如果id>已分配,则打印“下次重试”消息

答案 2 :(得分:0)

解决此问题的一种方法是拥有一个包含各个奖项记录的表格,并尝试通过以下查询来“认领”其中一个:

UPDATE prizes SET claimed_by=? WHERE prize_type=? AND claimed_by IS NULL LIMIT 1

如果您对UNIQUEprize_type施加claimed_by约束,那么这意味着没有人可以要求给定类型的奖品超过一个。这是在数据库级别强制执行的,不能被计时问题所规避。

调用该更新时,将修改零行或一行。检查结果的更新行数,以查看索赔是否成功。