Существует веб-приложение (Win7x64, MS Sql Server 2012, Apache 2.4, PHP 5.6, Yii 1.1).
Около сотни пользователей постоянно наполняют одну таблицу. В случае, если разные пользователи в одно и тоже время произвели вставку строки, первому возвращается ID вставленной строки второго и дальнейшие действия, например сохранение истории изменений, ведется с неверным ID.
Yii использует PDO, в частности метод lastInsertId для получения айдишника, но описания подобной проблемы в интернете не встретил.
Для чистоты эксперимента, я создал две новые таблица Collision и CollisionHistory:
CREATE TABLE [dbo].[Collision](
[id] [int] IDENTITY(1,1) NOT NULL,
[uuid] [varchar](50) NULL,
CONSTRAINT [PK_Collision] PRIMARY KEY CLUSTERED
(
[id] ASC
) ON [PRIMARY]
)
CREATE TABLE [dbo].[CollisionHistory](
[id] [int] IDENTITY(1,1) NOT NULL,
[collision_id] [int] NULL,
[uuid] [varchar](50) NULL,
CONSTRAINT [PK_CollisionHistory] PRIMARY KEY CLUSTERED
(
[id] ASC
)
)
На их основе в gii генерю модели.
В модель Collision добавляю код:
public function afterSave(){
parent::afterSave();
$history = new CollisionHistory();
$history->collision_id = $this->id;
$history->uuid = $this->uuid;
$history->save(false);
}
Пишу контроллер
class CollisionController extends CController
{
public function actionTest()
{
for ($i = 0; $i < 1000; $i++) {
$model = new Collision();
$model->uuid = $this->UUIDv4();
$model->save(false);
}
}
private function UUIDv4()
{
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
}
}
Затем на счет три с коллегой запускаем actionTest каждый со своего компьютера.
В итоге на 2000 инсертов в таблицу Collision 419 повторений collision_id в таблице CollisionHistory.
Почему такое происходит и как этого избежать?
Ответ
Провел тест на чистом PDO, поменял несколько версий, но результат тот же.
Метод PDO::lastinsertid возвращает самый последний id для таблицы, как функция MSSQL IDENT_CURRENT()
То есть, если между вставкой и получением id произошла вставка в другой сессии, то возвращается id из второй сессии.
Свою проблему решил созданием хранимой процедуры для вставок в самую часто-используемую таблицу, в которой выходной параметр заполняется функцией SCOPE_IDENTITY().
Кстати, используя выходной параметр в хранимой процедуре, не стоит забывать про инструкцию SET NOCOUNT ON, которая отключает вывод количества затронутых процедурой строк вместо выходного параметра.
Комментариев нет:
Отправить комментарий