如何在Yii2中简化并发保存



我的应用程序将API暴露给来自外部系统的同步数据。相应的控制器使用QueryParamAuth进行身份验证,经过一些初步检查后,用发布的数据填充ActiveRecord模型产品,并尝试使用$product->save()保存它

该模型的id字段是通过migrate/create自动生成的:

'id' => $this->primaryKey(),

现在,在初始同步中,所有产品数据都需要从外部系统同步到新的应用程序。因此,外部系统正在对上述控制器公开的URL进行多个异步调用。然而,在$product->save()期间,我反复收到以下两个SQL错误中的一个,导致在375个测试批中只有250个记录被保存。

我遇到的第一个也是最常见的错误是:

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '7' for key 'PRIMARY'
The SQL being executed was: INSERT INTO `product` (`sku`, `name`, `quantity`, `created_at`, `updated_at`, `created_by`, `updated_by`, `id`) VALUES (NULL, 'Copies', 0, NOW(), NOW(), 3, 3, 7)

另一个错误,我即将结束的批次是:

SQLSTATE[22003]: Numeric value out of range: 1264 Out of range value for column 'id' at row 1
The SQL being executed was: INSERT INTO `product` (`sku`, `name`, `quantity`, `created_at`, `updated_at`, `created_by`, `updated_by`, `id`) VALUES (NULL, 'Brochures', 0, NOW(), NOW(), 3, 3, 9223372036854775807)

请注意,该表最初是空的,并且创建了id为1的第一条记录。AUTO_INCREMENT id中不稳定的跳跃似乎是随机发生的。最后保存的记录带有id 1816263484。

如何避免这些SQL错误?

在Yii2存储库上提出这个问题并进行了大量诊断讨论后,发现模型中的以下代码对此负责:

public function behaviors()
{
return [
...
'uuid' => [
'class' => UUIDBehavior::className(),
'column' => 'id',
],
];
}

删除uuid行为规则后,问题消失了。

Michal Hynčica认为我正在设定id的值是正确的,尽管我不知道这里会发生这种情况。

特别感谢lubosdz在这里以及GitHub上提供的帮助。

我已经设法以一种巧妙的方式解决了它。将代码留在此处,以防对其他人有所帮助。

我已经用$this->attemptParallelSave($product);替换了$product->save();。此函数的代码如下:

private function attemptParallelSave($record)
{
try {
$saved = $record->save();
if (!$saved && $record->errors) {
throw new Exception(json_encode($record->errors));
}
} catch (yiidbIntegrityException $e) {
if ($record->isNewRecord) {
$record->id = null;
sleep(random_int(1, 5)); //Sleep for a random duration to reduce contention
$this->attemptParallelSave($record);
} else {
throw $e;
}
}
}

虽然这不如获得锁(比如使用互斥锁(那么优雅,但它工作正常,没有任何明显的性能影响。

最新更新