Auto-Increment & Keys
Every table needs a way to generate its own primary key values, and both engines automate it — but the keyword, the function you call afterward to retrieve the value you just inserted, and the scoping rules around that function are all different enough to trip up a direct port.
💡 The Bottom Line
IDENTITY(1,1) becomes AUTO_INCREMENT, and SCOPE_IDENTITY()
becomes LAST_INSERT_ID(). The biggest behavioral gap isn't the keyword — it's that
SCOPE_IDENTITY() is scoped per-batch/per-caller (it won't pick up an ID generated by a
trigger in a different scope), while LAST_INSERT_ID() is scoped per-connection:
it always returns the last auto-increment value generated by statements on that same connection,
regardless of scope, which means concurrent connections never see each other's values but a
trigger's insert on the same connection can shadow the one you meant to read.
Declaring an auto-incrementing primary key
CREATE TABLE Orders (
OrderId INT IDENTITY(1,1) PRIMARY KEY,
CustomerName NVARCHAR(100) NOT NULL
);
CREATE TABLE orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
customer_name VARCHAR(100)
CHARACTER SET utf8mb4 NOT NULL
);
📋 Key differences
IDENTITY(seed, increment)lets you set both the starting value and the step size inline; MySQL'sAUTO_INCREMENTtakes no arguments in the column definition — you set the starting value afterward withALTER TABLE orders AUTO_INCREMENT = 1000;, and the increment is a server-wide setting (auto_increment_increment), not a per-column one.- SQL Server allows exactly one
IDENTITYcolumn per table, and it doesn't have to be the primary key (though it usually is); MySQL'sAUTO_INCREMENTcolumn must be indexed — almost always as the primary key or at least part of one. - Both engines reject explicit inserts into the auto-generated column unless you override the
behavior — SQL Server needs
SET IDENTITY_INSERT Orders ON; MySQL just lets you insert an explicit value directly (including 0 or NULL to still trigger auto-generation), no special mode required.
Retrieving the value you just inserted
INSERT INTO Orders (CustomerName)
VALUES ('Ada Lovelace');
SELECT SCOPE_IDENTITY();
INSERT INTO orders (customer_name)
VALUES ('Ada Lovelace');
SELECT LAST_INSERT_ID();
⚠️ Gotcha
Don't reach for SQL Server's other identity functions out of habit. @@IDENTITY
returns the last identity value generated in the current session, across any scope —
including a value generated by a trigger — which is exactly the bug SCOPE_IDENTITY()
exists to avoid. MySQL only has the one function, LAST_INSERT_ID(), and it behaves
like @@IDENTITY (connection-wide, not scope-limited): if an AFTER INSERT
trigger on the same connection inserts into another auto-increment table, a subsequent
LAST_INSERT_ID() call can return that table's value instead of the one you expected.
When this matters, capture the ID immediately after the triggering statement, before anything
else runs on that connection.
Composite and non-integer keys
SQL Server and MySQL both support composite primary keys (multiple columns together) and both
let a primary key be a non-integer type like UNIQUEIDENTIFIER/CHAR(36) for
GUID-based keys — but only one column in a MySQL table may ever carry
AUTO_INCREMENT, and it must be part of a key. A common pattern ported from SQL Server
where an IDENTITY column is just a convenience surrogate alongside a separately-enforced
natural key works the same way in MySQL: keep the AUTO_INCREMENT INT as the primary key,
and add a separate UNIQUE index for the natural key (e.g. an order number or email).
🔧 Try It Yourself
Write a MySQL INSERT into a table with an AUTO_INCREMENT primary key,
followed immediately by SELECT LAST_INSERT_ID(); in the same session. Then, in a
second connection (a second terminal or client tab), insert a row into the same table and call
LAST_INSERT_ID() there too — confirm each connection only ever sees its own most
recent value, never the other connection's.
🙋 There Are No Dumb Questions
Q: If two people insert into the same MySQL table at the same time, can
LAST_INSERT_ID() return the wrong row's ID?
A: No — that's exactly the problem LAST_INSERT_ID() is designed to avoid.
It's scoped to the current connection/session, not to the table, so concurrent connections each
get their own private view of "the last value I generated," even though the underlying
AUTO_INCREMENT counter is shared and strictly increasing across the whole table. The
danger case isn't concurrency across connections — it's a trigger firing on the same
connection and quietly changing what "last" means before you read it.
✅ Quick Check
Why is LAST_INSERT_ID() generally safe to use even with many concurrent
connections inserting into the same table?
Next up: schemas and keys are in place — time to look at the procedural layer, where
CREATE PROCEDURE syntax and variable declarations diverge more than almost anything
else between the two engines.