我们有一个看起来像这样的表:
appointment_id | team_id
----------------|---------
1001 | 1
1005 | 4
1009 | 7
在此表中,appointment_id
是主要索引,team_id
只是常规索引。
创建表格的代码:
CREATE TABLE `appointment_primary_teams` (
`appointment_id` int(11) NOT NULL,
`team_id` int(11) NOT NULL,
PRIMARY KEY (`appointment_id`),
KEY `team_id` (`team_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
然而,偶尔会以下代码失败:
// Even though it looks like we are making 2 different PDO connections here
// the return is the same instance of PDO shared by 2 instances of a class for
// running queries. (It is how our system allows 2 different prepared queries
// at the same time)
$remove_query = database::connect('master_db');
$insert_query = database::connect('master_db');
$remove_query->prepare("
DELETE FROM `appointment_primary_teams` WHERE appointment_id = :appointment_id
");
$insert_query->prepare("
INSERT INTO `appointment_primary_teams` (
`appointment_id`,
`team_id`
) VALUES (
:appointment_id,
:team_id
)
");
// Looping through a list of appointment data
foreach($appointments as $appointment) {
// Runs fine
$remove_query->bind(':appointment_id', $appointment['id'], CAST_INT);
$remove_query->run();
// Occasionlly errors saying $appointment['id'] already exists
$insert_query->bind(':appointment_id', $appointment['id'], CAST_INT);
$insert_query->bind(':team_id', $appointment['team_id'], CAST_INT);
$insert_query->run();
}
确切的错误是:
Database Error: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '1001' for key 'PRIMARY'
起初,我认为这是我们的API中的竞争条件,用户双击提交按钮,但我们的系统记录所有请求,我可以确认用户只发送1个请求。
我假设这是由于MySQL中某种类型的竞争条件而失败,但是我不确定如何防止它。如果这是真的,我可以告诉脚本睡眠几毫秒,但这不是一个理想的解决方案,因为如果数据库挂起,问题就会出现回来。
我的问题:导致此问题的原因是什么,如何防止此错误?
这适用于Amazon RDS服务器(MySQL 5.6.27); PHP是在Amazon Linux AMI 2017.09版本的Ngnix 1.13.9上运行的7.0.27版本。
注意:部分代码已更改为删除专有信息并简化了问题,但我保留了代码的所有功能。
要清楚,尽管显示的代码只有一个PDO连接实例正在使用中。在运行this code之后,连接ID返回相同,这意味着它与MySQL的连接相同。
这最终在某种程度上是MySQL本身的竞争条件;我最好的猜测是竞争条件要么是在查询排队中(MySQL在查询完全运行之前返回PHP),要么是MySQL的内存中索引(其中MySQL不是按时更新索引)下一个查询运行)
我已经做了几个版本的测试,试图确保它们正在发生什么,并且所有的测试都指向了这一点。如果我不得不猜测这可能是由AWS的一个配置文件修复的,但此时我别无选择,只能采用tadman所建议的REPLACE INTO
语法。
答案 0 :(得分:1)
确定竞争条件的最可靠方法是首先避免出现排序问题。用一个查询替换这对查询:
INSERT INTO `appointment_primary_teams` (
`appointment_id`,
`team_id`
) VALUES (
:appointment_id,
:team_id
) ON DUPLICATE KEY UPDATE team_id=VALUES(team_id)
这是一个原子操作,它将插入记录或更新现有记录,不需要DELETE
。这是维护这些关系记录的一种很好的通用方法。
另一种选择是更为严厉的REPLACE INTO
方法:
REPLACE INTO `appointment_primary_teams` (
`appointment_id`,
`team_id`
) VALUES (
:appointment_id,
:team_id
)
这会踩踏任何现有记录。这样做的另一面是,它就像一个原子DELETE
/ INSERT
对,如果它们是PRIMARY KEY
,它们会分配新的AUTO_INCREMENT
值。在你的情况下它不是,所以这不是问题。
这种竞争条件的方式是INSERT
查询必须与DELETE
查询同时运行。只有在有两个连接的情况下才有可能,这可能是因为同时收到的两个请求都试图改变记录,或者因为单个实例以某种方式运行两个查询并行。