我的理解是 Apache 为每个传入请求创建一个单独的 PHP 进程。这意味着,如果我有执行以下操作的代码:
- 检查记录是否存在
- 如果记录不存在,则创建它
那么这很容易受到竞争条件的影响,不是吗?如果两个请求同时进入,并且它们都同时命中 (1),它们都会返回 false,然后都尝试插入新记录。
如果是这样,人们如何处理这个问题?围绕这两个请求创建一个 MySQL 事务会解决问题,还是我们需要做一个全表锁?
据我所知,您无法跨不同连接创建事务。也许一种解决方案是将您正在检查的列设置为唯一的。这样,如果有两个连接到10
, 并且10
不存在。他们都将尝试创建10
. 一个会先完成插入行,一切都很好;那么后面一秒钟的连接将失败,因为该列不是唯一的。如果您捕获了引发的异常,那么您随后可以SELECT
从数据库中获取记录。
老实说,我很少遇到这种情况。通常可以通过重新评估业务需求来缓解这种情况。即使两个不同的用户试图插入完全相同的数据,我也会推迟对用户重复的管理,而不是应用程序。
但是,如果有理由在应用程序逻辑中强制执行唯一约束,我会使用INSERT IGNORE... ON DUPLICATE KEY UPDATE...
查询(当然,在表中使用相应的 UNIQUE 索引)。
我认为在第二步处理错误应该就足够了。如果两个进程尝试创建一条记录,那么只要您正确配置了 MySQL 表,其中一个就会失败。在正确的字段中使用 UNIQUE 是解决问题的一种方法。
Apache 不会“为每个传入请求创建单独的 PHP 进程”。它要么使用进程池(默认,prefork 模式),要么使用线程。
正如您所提到的,竞争条件也可能被称为(或导致)数据库“死锁”。@看看什么是数据库中的死锁?
在需要的地方使用事务应该可以解决这个问题,是的。
通过确保检查记录是否存在并在事务中创建它,整个操作是原子的。
因此,其他请求不会尝试创建重复记录(或者,根据实际查询,创建不一致或进入实际死锁)。
另请注意,MySQL (尚)不支持嵌套事务:事务中不能有事务,因为第一次提交将提交所有内容。