谈论并发 PHP 时的最佳方法
Posted
技术标签:
【中文标题】谈论并发 PHP 时的最佳方法【英文标题】:Best approach when speaking about concurrency PHP 【发布时间】:2020-08-17 23:41:07 【问题描述】:我正在开发 php + mysql 项目,我们使用余额列来跟踪用户的余额。每当用户进行购买时,他们的余额都会被扣除。但是,我试图找出在尝试购买产品时处理来自同一用户的多个请求的最佳方法。 我做了一些研究并编写了以下两种方法,在这里我看不出有什么区别,谁能解释这两种方法之间的区别是什么,哪一种在并发方面最好?
Approach 1:
START transaction
"SELECT balance from users where user_id = 1 FOR UPDATE"
check if balance - product price is enough then UPDATE
commit
Approach 2:
"UPDATE users set balance = balance - 30 where user_id = 1 AND balance - 30 >= 0"
如您所见,选项 2 的代码更少,但我仍然看到很多人推荐第一种方法(先锁定行然后更新)。
谁能帮我理解这里的实际区别以及当您关心并发并希望避免多个请求可能使余额列无效时最好使用哪一个?如果您有更好的方法,请告诉我,也许我想太多了。我正在使用 PDO。
【问题讨论】:
【参考方案1】:任何一种解决方案都可以明确地防止竞争条件。 SQL 数据库具有原子性、一致性、隔离性和持久性 (ACID) 属性。 SQL 数据库的一个很酷的地方是:如果交易正确,您就不必担心竞争条件。
像第二个示例中的 UPDATE 这样的单语句操作始终是原子事务。而且,您(正确编写的)第一个示例中的显式事务也是原子事务。
与@GMB 一样,我更喜欢尽可能使用单语句操作。但这只是因为代码更容易让下一个程序员(或我未来的自己)理解。两种方法都有效。
而且,如果您的业务规则变得更加复杂,您可能需要多语句事务。这是您的第一种方法的优势。
您的两种解决方案都可以保留 ACID。
【讨论】:
实际上,从经验来看,第一个版本更容易理解和遵循,因为它遵循了如果我们是执行事务的文员你我会做什么的逻辑。第一个解决方案也更容易调试,因为选择就在代码中,而如果您需要调试第二个解决方案,您可能会编写一个单独的选择。不过,对于这种特殊情况,我也会选择第二个版本。【参考方案2】:第二种方法是正确的方法。这是一个查询,同时检查用户是否有足够的发现,并更新他们的余额。
数据库将在后台正确处理此查询的并发性,与第一种方法相反,第一种方法需要使用事务来避免竞争条件。该行在更新期间被锁定:如果多个会话尝试更新同一行,update
s 将按顺序执行,并且每个查询执行的更改都会影响以下查询。
在您的应用程序中,您通常会触发查询,然后检查一行是否受到影响。如果是,那么您知道更新已执行(因此用户有足够的发现) - 否则,交易被拒绝(因为用户不存在,或者因为它没有足够的发现)。
【讨论】:
那么第二种方法就足够好了,同时发生的 2 个查询不可能使余额为负数正确? @sharpness:是的,这就是我想要表达的观点。 所以数据库在进行单次更新时会锁定行,或者它是如何工作的?那么在什么情况下会使用第一种方法呢? @sharpness:第一个解决方案的问题是另一个会话可能会在读取它的查询和更新它的查询之间更新同一行。这是一个竞争条件,为避免它,您需要一个事务(将行锁定更长时间)。第二种方法没有这个问题。这是一个可以满足您需求的单一查询,因此在我看来这是最好的选择。 该行在第一种方法中被锁定,因为我使用的是 SELECT FOR UPDATE,这会阻止其他事务更新和读取该行还是我错了?以上是关于谈论并发 PHP 时的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章