A
A
Artem Loktionov2015-09-23 11:04:17
Yii
Artem Loktionov, 2015-09-23 11:04:17

Why does PDO::lastInsertId return the wrong ID on frequent database inserts?

There is a web application (Win7x64, MS Sql Server 2012, Apache 2.4, PHP 5.6, Yii 1.1).
About a hundred users constantly fill one table. If different users have inserted a row at the same time, the ID of the inserted row of the second is returned to the first and further actions, for example, saving the history of changes, are carried out with an incorrect ID.
Yii uses PDO, in particular the lastInsertId method to get the ID, but I have not seen a description of a similar problem on the Internet.
Why does this happen and how to avoid it?
Thanks in advance.
upd.
Simplified code looks like this:

//Контроллер
public function actionCreate(){
    $model = new Pacient();
    $model->attributes = $_POST['Pacient'];
    $model->save();
}
//Модель
public function beforeSave(){
    if (empty($this->NNAPR)) {
            if (!$this->setNNAPR())
                return false;
        }
    return parent::beforeSave();
}
public function afterSave(){
    parent::afterSave();
    $history = new PacientHistory();
    //здесь уже неверный ID
    $history->ID_PACIENT = $this->ID;
    $history->save();
}

Records are added not only through the form, but are also exported as xml files, but the code used is almost the same. The situation is repeated for files and for manual input.
For 2000 records added per day, there are between 0 and 3 such errors.
upd.2
For the purity of the experiment, I created two new tables Collision and 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
  )
)

Based on them, I generate models in gii.
I add the code to the Collision model:
public function afterSave(){
        parent::afterSave();
        $history = new CollisionHistory();
        $history->collision_id = $this->id;
        $history->uuid = $this->uuid;
        $history->save(false);
    }

I am writing a controller
class CollisionController extends CController
{

    public function actionTest()
    {
        for ($i = 0; $i < 1000; $i++) {
            $model = new Collision();
            $model->uuid = $this->UUIDv4();
            $model->save(false);
        }
    }

    //метод с php.net
    private function UUIDv4()
    {
        return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',

            // 32 bits for "time_low"
            mt_rand(0, 0xffff), mt_rand(0, 0xffff),

            // 16 bits for "time_mid"
            mt_rand(0, 0xffff),

            // 16 bits for "time_hi_and_version",
            // four most significant bits holds version number 4
            mt_rand(0, 0x0fff) | 0x4000,

            // 16 bits, 8 bits for "clk_seq_hi_res",
            // 8 bits for "clk_seq_low",
            // two most significant bits holds zero and one for variant DCE1.1
            mt_rand(0, 0x3fff) | 0x8000,

            // 48 bits for "node"
            mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
        );
    }

}

Then, on the count of three, with a colleague, we launch actionTest each from their own computer.
As a result, for 2000 inserts into the Collision table, there are 419 collision_id repetitions in the CollisionHistory table.
The test on pure PDO gave the same result, the last ID is returned for another entry.

Answer the question

In order to leave comments, you need to log in

4 answer(s)
A
Artem Loktionov, 2015-09-24
@loktionov

I did a test on pure PDO, changed several versions, but the result is the same. The PDO::lastinsertid method returns the most recent id for the table, like the MSSQL IDENT_CURRENT() function.
That is, if an insert occurred in another session between the insert and getting the id, then the id from the second session is returned.
I solved my problem by creating a stored procedure for inserts into the most frequently used table, in which the output parameter is filled with the SCOPE_IDENTITY () function.
By the way, when using an output parameter in a stored procedure, do not forget about the SET NOCOUNT ON statement, which disables the display of the number of rows affected by the procedure instead of the output parameter.

A
Alexander Aksentiev, 2015-09-23
@Sanasol

It's impossible.
lastinsert returns the last entry for the current database connection. Which cannot be in any other user.
So that's not the error.
Unless of course you did not check by opening two tabs in one browser :D

P
Pavel Volintsev, 2015-09-23
@copist

Whether at you such, what the request interposes some records at once?
Then the ID of the most recently inserted
PS will be returned. By the way, the UUIDv4 method used is not the most reliable in terms of key uniqueness

N
Nikolai Matyushenkov, 2015-10-01
@mnv

Have you tried wrapping insert requests and getting the last id in a transaction?

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question