What is a UUID in SQL
Storing a UUID as text is the most common expensive mistake. PostgreSQL has a native uuid type of exactly 16 bytes; MySQL 8 offers BINARY(16) with UUID_TO_BIN(); SQL Server has uniqueidentifier. All three are smaller and faster to compare than a 36-character string, and all three validate on write.
Choosing the version matters more in a database than anywhere else, because of the index. Random v4 values scatter across a B-tree, so every insert touches a different page; a time-ordered v7 keeps new rows together and inserts stay cheap. PostgreSQL 18 ships uuidv7() for exactly that reason; on older versions or other engines you generate v7 in the application and insert it as a parameter.
How to
- Pick the function for your engine —
uuidv7(),gen_random_uuid(),UUID()orNEWID(). - Make it the column default so inserts never have to supply a value.
- Keep the storage type native (16 bytes) rather than text, and use v7 when the column is indexed.
Use cases
- Primary keys that must be generated without asking the database, for example in a distributed service.
- Idempotent imports, where a deterministic v5 derived from the source row prevents duplicates on re-run.
- Public identifiers that should not reveal how many rows the table holds, unlike an auto-increment column.
Compare UUID in SQL with other formats
| Option | When to use |
|---|---|
PostgreSQL uuid | Native 16-byte type, validated on write; <code>uuidv7()</code> from version 18. |
MySQL BINARY(16) | Compact but needs <code>UUID_TO_BIN(uuid, 1)</code> to reorder the timestamp for index locality. |
SQL Server uniqueidentifier | Native type; <code>NEWID()</code> is v4, <code>NEWSEQUENTIALID()</code> is ordered. |
Text column | 36 characters, no validation, slower comparisons — avoid unless the engine offers nothing else. |
Code examples
PostgreSQL
-- Random v4 (pgcrypto, or built in since PostgreSQL 13)
SELECT gen_random_uuid();
-- Time-ordered v7 (PostgreSQL 18+)
SELECT uuidv7();
-- Column default + metadata queries
CREATE TABLE events (
id uuid PRIMARY KEY DEFAULT uuidv7(),
created_at timestamptz NOT NULL DEFAULT now()
);
SELECT uuid_extract_version(id), uuid_extract_timestamp(id) FROM events;
MySQL
SELECT UUID(); -- v1, time based
SELECT UUID_TO_BIN(UUID(), 1); -- v1 bytes, time part first: index friendly
CREATE TABLE events (
id BINARY(16) PRIMARY KEY DEFAULT (UUID_TO_BIN(UUID(), 1)),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
SELECT BIN_TO_UUID(id, 1) FROM events; -- back to text
SQL Server
SELECT NEWID(); -- v4, random
SELECT NEWSEQUENTIALID(); -- ordered, but only allowed as a column default
CREATE TABLE events (
id UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID(),
created_at DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME(),
CONSTRAINT pk_events PRIMARY KEY CLUSTERED (id)
);
Deterministic keys
-- PostgreSQL: derive a stable id from a natural key
SELECT md5('tenant:42')::uuid;
-- Safer: keep the key stable with a namespace UUID
SELECT uuid_generate_v5(uuid_ns_url(), 'https://example.com/tenant/42');
Frequently asked questions
Should I use UUID or an auto-increment integer as a primary key?
Use an integer when the table is local, small and you never merge data from several databases — it is smaller and needs no coordination. Use a UUID when identifiers must be minted by several services, when rows are imported from elsewhere, or when the key is exposed publicly and should not reveal row counts or ordering.
Why is my UUID insert slower than my integer insert?
Almost always because of index locality. Random v4 keys cause page splits throughout the B-tree. Switch the column to a time-ordered v7 (or NEWSEQUENTIALID(), or UUID_TO_BIN(uuid, 1) in MySQL) and inserts become near-sequential again.
Can I generate a UUID in the database but keep the application portable?
Yes: generate it in the application and pass it as a parameter. The database then only stores and validates it, so the same code works on PostgreSQL, MySQL and SQL Server, and the identifier is known before the INSERT — which is what makes idempotent retries easy.