Страницы

Поиск по вопросам

вторник, 16 июля 2019 г.

Почему PDO::lastInsertId возвращает неверный ID при частых вставках в БД?

Существует веб-приложение (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, которая отключает вывод количества затронутых процедурой строк вместо выходного параметра.

Комментариев нет:

Отправить комментарий