MySQL Get Last ID

sa sahaya jerish Updated 20 Oct 2026
5 min read ·Lesson 149 of 155
In this post

Why You Need the Last Inserted ID In simple terms: When you insert a new row into a table with an AUTO_INCREMENT column, MySQL chooses the ID for you. Very often, you need that ID immediately: to redi

Why You Need the Last Inserted ID

In simple terms: When you insert a new row into a table with an AUTO_INCREMENT column, MySQL chooses the ID for you. Very often, you need that ID immediately: to redirect the user to their new order's page, to insert the order's items (which must reference the order), or to log what was created.

Technically: After an INSERT into a table with an AUTO_INCREMENT column, mysqli::$insert_id (or mysqli_stmt::$insert_id for prepared statements) returns the generated value. In SQL, the same value is available through LAST_INSERT_ID(). The value is tracked per connection, so concurrent users never see each other's IDs.


Reading insert_id

<?php
require __DIR__ . '/src/db.php';

$db = db();

$db->execute_query(
    'INSERT INTO customers (name, email, city) VALUES (?, ?, ?)',
    ['Farah', 'farah@example.com', 'Vellore']
);

$customerId = $db->insert_id;

echo "New customer ID: $customerId\n";
var_dump(is_int($customerId));

 

Output (example)

New customer ID: 7
bool(true)

 

How It Works

  1. MySQL generates the next AUTO_INCREMENT value while inserting the row.
  2. insert_id reads the value generated by the most recent INSERT on this connection.
  3. The value is an integer (it is returned as a string only if it exceeds PHP's maximum integer).

(The ID is 7 rather than 6 because the failed duplicate insert in MySQL Insert Data used up the value 6 — see "Gaps in IDs Are Normal" below.)

When insert_id Is 0

Situationinsert_id
The last query was not an INSERT (or UPDATE of an auto-increment column)Unchanged / 0
The table has no AUTO_INCREMENT column0
An INSERT ... ON DUPLICATE KEY UPDATE updated an existing rowThe existing row's ID is not returned reliably; query it if needed

 

Read insert_id immediately after the insert, before running other queries on the same connection.


Why Not SELECT MAX(id)?

A common mistake is to look up the newest ID after inserting:

// WRONG
$db->execute_query('INSERT INTO orders (customer_id) VALUES (?)', [1]);
$orderId = $db->query('SELECT MAX(id) FROM orders')->fetch_column();

 

If two customers place orders at almost the same time, both requests may read the same maximum ID — one of them receives the other customer's order number. insert_id and LAST_INSERT_ID() are tied to the connection that performed the insert, so they are always correct, even under heavy concurrency.


Practical Example: Creating an Order With Its Items

An order is stored in two tables: one row in orders and several rows in order_items that reference it. The order's ID is needed to insert the items, and everything should succeed or fail together, so a transaction wraps the steps:

<?php
require __DIR__ . '/src/db.php';

function createOrder(mysqli $db, int $customerId, array $items): int
{
    $db->begin_transaction();

    try {
        $db->execute_query('INSERT INTO orders (customer_id, status) VALUES (?, ?)', [$customerId, 'pending']);
        $orderId = $db->insert_id;

        $total = 0;
        foreach ($items as $item) {
            $db->execute_query(
                'INSERT INTO order_items (order_id, product_id, quantity, unit_price) VALUES (?, ?, ?, ?)',
                [$orderId, $item['product_id'], $item['quantity'], $item['unit_price']]
            );
            $total += $item['quantity'] * $item['unit_price'];
        }

        $db->execute_query('UPDATE orders SET total = ? WHERE id = ?', [$total, $orderId]);

        $db->commit();
        return $orderId;
    } catch (Throwable $e) {
        $db->rollback();
        throw $e;
    }
}

$db = db();
$db->query('ALTER TABLE orders AUTO_INCREMENT = 501');   // one-time setup: start order numbers at 501

$orderId = createOrder($db, 1, [
    ['product_id' => 2, 'quantity' => 3, 'unit_price' => 60.00],    // Notebook A5
    ['product_id' => 1, 'quantity' => 10, 'unit_price' => 20.00],   // Gel Pen
]);

$order = $db->execute_query('SELECT id, status, total FROM orders WHERE id = ?', [$orderId])->fetch_assoc();
echo "Created order #{$order['id']} ({$order['status']}), total ₹{$order['total']}\n";

 

Output (with the products from MySQL Insert Multiple in place)

Created order #501 (pending), total ₹380.00

 

Step-by-Step Explanation

  1. ALTER TABLE ... AUTO_INCREMENT = 501 sets the starting value, so order numbers do not start at 1 (a one-time setup step, normally done in a migration).
  2. Inside the transaction, the order row is inserted, and insert_id provides its ID — 501.
  3. Each item is inserted with that ID as order_id; the foreign key guarantees the order exists.
  4. The total (3 × 60 + 10 × 20 \= 380) is saved on the order.
  5. commit() makes all changes permanent at once. If any step throws (for example, an invalid product ID violating a foreign key), rollback() undoes everything — no half-created order remains. Transactions are covered in PDO Transactions.
  6. The total column is DECIMAL, which MySQLi returns as the string "380.00", preserving exact precision.

In a real shop, unit prices are read from the products table on the server, never taken from the client.


insert_id With Prepared Statements

When using prepare(), the statement object has its own insert_id:

<?php
$stmt = $db->prepare('INSERT INTO customers (name, email) VALUES (?, ?)');
$stmt->execute(['Kumar', 'kumar@example.com']);

echo $stmt->insert_id . "\n";   // same value as $db->insert_id

 


Multi-Row Inserts and LAST_INSERT_ID()

For an INSERT with several rows, insert_id returns the ID of the first inserted row:

<?php
$db->query("INSERT INTO customers (name, email) VALUES ('Lakshmi', 'lakshmi@example.com'), ('Mani', 'mani@example.com')");

$firstId = $db->insert_id;
$count = $db->affected_rows;

echo "Inserted IDs $firstId to " . ($firstId + $count - 1) . "\n";

 

Output (example)

Inserted IDs 9 to 10

 

With InnoDB's default settings, the IDs of a single multi-row insert are consecutive, so the range can be calculated. Inside SQL, LAST_INSERT_ID() returns the same value and can be used directly in follow-up statements:

INSERT INTO orders (customer_id) VALUES (1);
INSERT INTO order_items (order_id, product_id, quantity, unit_price)
VALUES (LAST_INSERT_ID(), 3, 1, 1299.00);

 

Gaps in IDs Are Normal

Auto-increment values are not reused. Failed inserts (for example, a duplicate email), rolled-back transactions, and deleted rows all leave gaps in the sequence. IDs identify rows; they are not a count and should never be expected to be continuous. Use a separate column if you need gap-free invoice numbers (a legal requirement in some countries).


Common Mistakes

Using SELECT MAX(id)

Effect: Wrong IDs under concurrent requests.

Fix: Use insert_id or LAST_INSERT_ID().

Reading insert_id Too Late

Effect: Another INSERT on the same connection has overwritten the value.

Fix: Store insert_id in a variable immediately after the insert.

Effect: A failure leaves an order without items (or items without an order).

Fix: Wrap related inserts in a transaction.

Expecting Continuous IDs

Effect: Confusion about "missing" IDs.

Fix: Treat IDs as identifiers only; use dedicated numbering for gap-free sequences.


Best Practices

  • Read insert_id directly after each insert and store it.
  • Use the new ID to insert related rows and to redirect users to the created resource.
  • Wrap multi-table inserts in a transaction.
  • Never derive new IDs from MAX(id) or counts.
  • Accept gaps in auto-increment sequences.

Official reference: https\://www.php.net/manual/en/mysqli.insert-id.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Home PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else Slicing Strings User-defined Functions Slicing Strings PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning MySQL Database MySQL Connect MySQL Create DB MySQL Create Table MySQL Insert Data MySQL Get Last ID MySQL Insert Multiple MySQL Prepared MySQL Select Data MySQL Where Debugging PHP Project: Image Upload Gallery
Ask about this post
AI Ask about this post

Ask questions about MySQL Get Last ID and get answers drawn from it.

Signed-in readers only.