PDO::lastInsertId() SafelyAfter inserting a row into a MySQL table with an AUTO_INCREMENT primary key, $pdo->lastInsertId() returns the newly generated ID as a string. You commonly need that ID immediately to insert child rows—such as saving order_items linked to a newly created orders row.
lastInsertId() is scoped strictly to your current database connection, so concurrent inserts from other visitors on different connections will never mix up your ID. Wrap parent and child inserts inside $pdo->beginTransaction() and $pdo->commit() so both succeed or roll back together.
<?php
declare(strict_types=1);
function createOrderWithItems(PDO $pdo, int $userId, array $items): int
{
$pdo->beginTransaction();
try {
$total = array_sum(array_map(fn (array $item): float => $item['qty'] * $item['price'], $items));
$orderStmt = $pdo->prepare(
'INSERT INTO orders (user_id, total_amount) VALUES (:user_id, :total_amount)'
);
$orderStmt->execute([
':user_id' => $userId,
':total_amount' => round($total, 2),
]);
// Cast the returned numeric string to int immediately
$orderId = (int) $pdo->lastInsertId();
$itemStmt = $pdo->prepare(
'INSERT INTO order_items (order_id, sku, quantity, unit_price) VALUES (:order_id, :sku, :qty, :price)'
);
foreach ($items as $item) {
$itemStmt->execute([
':order_id' => $orderId,
':sku' => $item['sku'],
':qty' => $item['qty'],
':price' => $item['price'],
]);
}
$pdo->commit();
return $orderId;
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
throw $e;
}
}
If you insert multiple rows in a single INSERT INTO ... VALUES (...), (...) query, MySQL's lastInsertId() returns the ID of the first row in that batch, not the last row. Also, lastInsertId() returns '0' if the table has no AUTO_INCREMENT column (such as UUID primary keys).
lastInsertId() after commit(): Wait until before commit()? Actually in PDO MySQL, calling lastInsertId() right after $stmt->execute() inside the transaction is safest—some drivers reset the value after commit().INSERT before reading lastInsertId(): Each INSERT overwrites the connection's last insert ID. Store it in $orderId immediately after the parent insert.lastInsertId() without (int) cast: PDO::lastInsertId() returns a string (to support 64-bit IDs on 32-bit PHP), so cast (int) when your method declares : int.PDO::lastInsertId() safe under high concurrent traffic?Yes. MySQL tracks LAST_INSERT_ID() per TCP connection session, so User A's connection never sees User B's inserted ID.
lastInsertId() return if an INSERT IGNORE skipped a duplicate row?It returns '0' because no new auto-increment row was created.
INSERT ... ON DUPLICATE KEY UPDATE update lastInsertId()?Only if a new row was inserted (or if you explicitly pass id = LAST_INSERT_ID(id) in the update clause).
Sign in to track your learning journey, earn industry-recognized certificates, and join our elite developer community.
Quick Access With
Experiment with the code from this lesson in our interactive playground.