Skip to content
100% in your browser. Nothing you paste is uploaded — all processing runs locally. Read more →

UUIDs across SQL databases

On this page
  1. Side-by-side
  2. PostgreSQL
  3. MySQL 8+
  4. MariaDB 10.7+
  5. SQL Server
  6. SQLite
    1. As BLOB (16 bytes — recommended)
    2. As TEXT (37 chars)
  7. Oracle 23+
  8. Indexing
  9. Storage comparison
  10. Migration: text → uuid
  11. Migration: v4 → v7
  12. Common pitfalls
  13. Try the tools

Every major SQL database supports UUIDs, but the spelling, storage, and recommended generator differ. This page is the side-by-side cheatsheet. For the deeper reasoning behind any of it, see UUID as primary key.

Side-by-side

EngineUUID typeBytesRandom generatorTime-ordered
PostgreSQL 18+uuid16gen_random_uuid()uuidv7()
PostgreSQL 13–17uuid16gen_random_uuid()generate in app
PostgreSQL ≤12uuid (with pgcrypto)16gen_random_uuid()generate in app
MySQL 8+BINARY(16) + UUID_TO_BIN/BIN_TO_UUID16UUID()generate in app
MariaDB 10.7+UUID (native)16UUID()generate in app
SQL Serveruniqueidentifier16NEWID() (v4)NEWSEQUENTIALID() (proprietary)
SQLiteBLOB(16) or TEXT16 / 37none built-ingenerate in app
Oracle 23+UUID (native)16SYS_GUID() (proprietary, not RFC-compliant)generate in app

The general advice is the same across all of them: store as 16 bytes, use the native type, and generate v7 in your application code if the engine doesn’t have a built-in v7 generator.

PostgreSQL

-- PG 18+ (October 2025): time-ordered, recommended
CREATE TABLE orders (
    id uuid PRIMARY KEY DEFAULT uuidv7(),
    created_at timestamptz NOT NULL DEFAULT now()
);

-- PG 13-17: random
CREATE TABLE orders (
    id uuid PRIMARY KEY DEFAULT gen_random_uuid()
);

-- PG ≤12: needs pgcrypto extension
CREATE EXTENSION IF NOT EXISTS pgcrypto;

See the dedicated PostgreSQL UUID guide.

MySQL 8+

MySQL has a UUID() function returning v1, but no native UUID type. The recommended pattern is BINARY(16):

CREATE TABLE orders (
    id BINARY(16) PRIMARY KEY,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

-- generate in MySQL (v1, not recommended for new code)
INSERT INTO orders (id) VALUES (UUID_TO_BIN(UUID()));

-- read as string
SELECT BIN_TO_UUID(id) AS id FROM orders;

For v7, generate in your application:

INSERT INTO orders (id) VALUES (UUID_TO_BIN(?));
-- bind the v7 UUID generated in app code

UUID_TO_BIN(uuid, 1) reorders bytes for better B-tree locality with v1 UUIDs (the second arg is “swap_flag”). Don’t use it with v7 UUIDs — v7 already has the right byte order for sorting.

MariaDB 10.7+

MariaDB added a native UUID type that stores 16 bytes:

CREATE TABLE orders (
    id UUID PRIMARY KEY DEFAULT UUID(),
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

Better than MySQL for ergonomics — no UUID_TO_BIN round-tripping. But the default UUID() is still v1; for v7, generate in app code.

SQL Server

-- v4 random (don't use as clustered primary key — fragments the index)
CREATE TABLE Orders (
    Id uniqueidentifier PRIMARY KEY DEFAULT NEWID(),
    CreatedAt datetime2 NOT NULL DEFAULT SYSDATETIME()
);

-- "sequential" GUID — better for clustered index, but reveals partial timestamp
CREATE TABLE Orders (
    Id uniqueidentifier PRIMARY KEY DEFAULT NEWSEQUENTIALID(),
    CreatedAt datetime2 NOT NULL DEFAULT SYSDATETIME()
);

-- v7 from .NET 9+ application code (recommended)
INSERT INTO Orders (Id, CreatedAt) VALUES (@id, @now);
-- @id from Guid.CreateVersion7()

NEWSEQUENTIALID() is not a UUID v7. It’s a SQL Server-specific monotonic-within-a-machine variant. For cross-platform v7, generate in C# / .NET 9+ — see the C# / .NET guide.

A SQL Server-specific gotcha: stored bytes are in Microsoft byte order (first three groups little-endian). When reading raw bytes from another platform, byte-swap. See UUID vs GUID.

SQLite

SQLite has no UUID type. Two patterns:

CREATE TABLE orders (
    id BLOB(16) PRIMARY KEY,
    created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
);

Half the storage of TEXT, faster comparisons. Your application generates UUIDs and inserts the binary form.

As TEXT (37 chars)

CREATE TABLE orders (
    id TEXT PRIMARY KEY,
    created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
);

Easier to debug (legible in sqlite3 REPL), but ~2× storage. Fine for small databases.

For both, generate v7 in your application — SQLite has no built-in UUID functions.

Oracle 23+

Oracle 23 added a native UUID type:

CREATE TABLE orders (
    id UUID DEFAULT SYS_GUID() PRIMARY KEY,
    created_at TIMESTAMP DEFAULT SYSTIMESTAMP
);

Note: SYS_GUID() is a proprietary 16-byte value, not a strict RFC 4122 UUID. The version and variant bits are not set. If you need standard RFC UUIDs in Oracle, generate in your application code and insert as UUID.

For Oracle 21 and earlier, use RAW(16):

CREATE TABLE orders (
    id RAW(16) PRIMARY KEY,
    created_at TIMESTAMP DEFAULT SYSTIMESTAMP
);

Indexing

Every engine indexes UUIDs the same way: B-tree on the native type. The performance differences are entirely about whether the UUIDs are random (v4 — bad for clustered indexes) or sequential (v7, NEWSEQUENTIALID — good).

For non-clustered or hash-partitioned indexes, v4 is fine — the page-split problem only applies to ordered storage like a clustered primary key.

Storage comparison

ApproachBytes per ID
Native UUID type16 (recommended everywhere)
BINARY(16) / BLOB(16) / RAW(16)16
CHAR(36) (with hyphens)36
VARCHAR(37) (canonical)~37
CHAR(32) (no hyphens)32
TEXT storing canonical~37

The 2× storage difference between native (16 bytes) and text (~37 bytes) compounds at every level: row size, index size, working-set memory, replication bandwidth.

Migration: text → uuid

Common scenario — historical schema stored UUIDs as varchar(36) or text. Migrating to the native type:

-- PostgreSQL
ALTER TABLE users
ALTER COLUMN id TYPE uuid USING id::uuid;

-- SQL Server (more involved, requires recreating the column)
ALTER TABLE Users ADD IdNew uniqueidentifier NULL;
UPDATE Users SET IdNew = CAST(Id AS uniqueidentifier);
-- then drop and rename

Test on a copy first. Some engines need a brief table lock for this.

Migration: v4 → v7

Don’t backfill old rows. Just change the default for new ones:

ALTER TABLE orders ALTER COLUMN id SET DEFAULT uuidv7(); -- PG 18+

Existing v4 IDs stay v4 forever. New rows get v7. The version is encoded in each ID, so anything that needs to know can check.

Common pitfalls

Try the tools