Apache,MySQL和PHP中的竞争条件

3
我了解的是,Apache会为每个请求创建一个独立的PHP进程。这意味着,如果我有像下面这样的代码:
  • 检查记录是否存在
  • 如果不存在,则创建它
那么,这段代码就容易发生竞态条件,对吧?如果两个请求同时到达,并且同时命中(1),那么它们都会返回false,接着都试图插入一条新记录。
如果是这样的话,人们该如何处理呢?将这两个请求放到MySQL事务中是否可以解决问题,还是需要进行完整的表锁定?

使用INSERT IGNORE、LOCK TABLE或在不想重复的列上使用UNIQUE索引都是不错的选择。 - guido
请参见https://dev59.com/cHM_5IYBdhLWcg3wfTE0。 - Matt Browne
SELECT ... FOR UPDATE - Karoly Horvath
4个回答

3
据我所知,您无法在不同的连接之间创建事务。也许一个解决方案是将您正在检查的列设置为唯一的。这样,如果两个连接都连接到“10”,而“10”不存在,则它们都将尝试创建“10”。其中一个将首先完成插入行的操作,一切顺利;然后稍晚一秒的连接将失败,因为该列不是唯一的。如果捕获抛出的异常,那么您随后可以从数据库中选择记录。

2

说实话,我很少遇到这种情况。通常重新评估业务需求就可以缓解这种情况。即使两个不同的用户尝试插入完全相同的数据,我也会将重复数据的管理推迟给用户,而不是应用程序。

然而,如果有理由在应用程序逻辑中强制执行唯一约束,则我将使用一个INSERT IGNORE... ON DUPLICATE KEY UPDATE...查询(当然,要与表中相应的唯一索引配合)。


0

我认为在第二步处理错误应该是足够的。如果两个进程尝试创建一条记录,那么其中一个将会失败,只要你已经适当地配置了MySQL表。在正确的字段上使用UNIQUE是一个解决方法。


0

Apache 不会为每个传入请求创建一个单独的 PHP 进程, 它使用进程池(默认的 prefork 模式)或线程。

正如你提到的,竞态条件也可能被称为(或引起)数据库“死锁”。 @see 什么是数据库死锁?

在需要时使用事务应该可以解决这个问题。
通过确保您在事务内检查记录是否存在并创建记录,整个操作就是原子的。
因此,其他请求将不会尝试创建重复的记录(或者根据实际查询创建不一致或进入实际死锁)。

还要注意,MySQL(尚)不支持嵌套事务: 您不能在事务内部再嵌套事务,因为第一个提交将提交所有内容。


3
这是一个简单的竞态条件,与死锁无关。 - Karoly Horvath
3
仅使用交易并不能解决这个问题,因为两个交易都可能看到记录还不存在的情况。 - Karoly Horvath
@Karoly Horvath:是的,进行了一些编辑以获得更清晰的表达。这个例子不会导致死锁,但通常情况下竞争条件可能会促成死锁,具体取决于实际的数据库/代码情况。 - J.C. Inacio
不,它仍然在许多层面上完全误导人。谁点赞了这个?好吧,我看到其中一个... :D - Karoly Horvath

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接