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

PHP/Mysql并发数据库调用问题,php,mysql,database,Php,Mysql,Database,在PHP中使用innoDB表和mysqli包装器进行查询 我们目前遇到了一个问题,即每秒请求相同脚本1500次的流量激增 情况是,前X个访问该脚本的用户数量将获得奖励 奖品是一张桌子上的一张记录,上面有领取奖品和被拍卖奖品的数量 一旦使用金额>=累计金额,我们将停止颁奖 发生的情况是,在脚本的其他实例可以更新该行之前,对脚本的多个请求正在同时读取该行,从而向每个脚本实例显示仍有许多奖品有待领取。这导致我们的奖励超过了实际金额 有没有关于如何避免这种情况的想法?对,你描述的是一种典型的比赛条件 一

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

我们目前遇到了一个问题,即每秒请求相同脚本1500次的流量激增

情况是,前X个访问该脚本的用户数量将获得奖励

奖品是一张桌子上的一张记录,上面有领取奖品和被拍卖奖品的数量

一旦使用金额>=累计金额,我们将停止颁奖

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


有没有关于如何避免这种情况的想法?

对,你描述的是一种典型的比赛条件

一种解决方案是在更新奖品表之前,使用SELECT…FOR UPDATE对该行建立锁。InnoDB将建立锁请求的顺序,使每个请求等待轮到它,直到它能够获得锁为止

然而,这不是一个好的解决方案,因为它会导致每个用户的浏览器旋转,等待响应。在服务器上,每秒排队时将快速收到1500个锁请求。即使每个会话只需要10毫秒就可以完成SELECT FOR UPDATE和后续更新,这已经是相当雄心勃勃了,但仍然需要每秒完成15.0秒的工作。到第2秒,您将有30.0秒的工作要做

与此同时,用户正在查看他们的浏览器,直到轮到他们为止。这几乎是一个破坏交易的设计

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

全球的 原子的 快速的 异步的 您可以让每个并发请求使用自动增量键插入表,这将保证它们的顺序。然后,一旦有X行,后续请求就不需要再插入任何行


另一种方法是使用消息队列。每个请求只是将自己的请求推送到队列中。然后,一个消费者从队列中取出第一个X请求,并向它们颁发奖品。队列中的其余请求被转储,它们不会获得奖励。

虽然您没有提供代码、表结构或更深入的数据库信息,但在这些情况下,第一个建议是使用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_尝试表,其中包含一个自动递增的主键和一个引用用户某些信息的字段

在每次点击时,插入一条记录,而不考虑可用库存,并检索插入行的ID。
然后,将检索到的ID与分配的奖励编号进行比较。如果已分配,请打印“下次重试”消息解决此问题的一种方法是创建一个包含单个奖金记录的表,并尝试使用如下查询来声明其中一个奖金记录:

UPDATE prizes SET claimed_by=? WHERE prize_type=? AND claimed_by IS NULL LIMIT 1
如果对奖品类型和已领取的奖品类型施加唯一约束,则表示任何人都不能领取给定类型的多个奖品。这是在数据库级别强制执行的,不能通过计时问题来规避


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

请共享您的架构、php代码和sql查询,也请共享您的代码。欢迎来到疯狂的世界。谢谢,我们使用了此版本的变体和一些我们自己的调整,以使其按我们需要的方式工作。