# MySQL to DSQL: AUTO_INCREMENT Migration

Part of [MySQL to DSQL DDL Migration](ddl-operations.md). See [Common Verify & Swap Pattern](ddl-operations.md#common-verify--swap-pattern) for the shared migration end-pattern.

---

## AUTO_INCREMENT Migration

**MySQL syntax:**

```sql
CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(255)
);
```

DSQL provides three options for replacing MySQL's AUTO_INCREMENT. Choose based on your workload requirements. See [Choosing Identifier Types](../auth/scaling-guide.md#choosing-identifier-types) in the scaling guide for detailed guidance.

**When choosing integer auto-increment, ALWAYS use `GENERATED AS IDENTITY`** (not `SERIAL`, which DSQL does not support). UUIDs (Option 1) remain the recommended default.

### Option 1: UUID Primary Key (Recommended for Scalability)

UUIDs are the recommended default because they avoid coordination and scale well for distributed writes.

```sql
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name VARCHAR(255)
);
```

> **DSQL: `gen_random_uuid()` is built-in; do NOT run `CREATE EXTENSION pgcrypto`.** DSQL ships
> PostgreSQL 16's core `gen_random_uuid()`, so the extension is unnecessary AND `CREATE EXTENSION`
> is rejected by DSQL (`ERROR: unsupported statement: CreateExtension`). Other `pgcrypto`
> functions (`crypt()`, `digest()`, `hmac()`, etc.) are unavailable — implement those at the
> application layer.

### Option 2: IDENTITY Column (Recommended for Integer Auto-Increment)

Use `GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY` when compact, human-readable integer IDs
are needed.

> **DSQL: `CACHE` is mandatory.** DSQL has **no implicit default** and rejects identity columns
> declared without it: `ERROR: identity column is not supported without an explicit cache size.
> please define CACHE greater than or equal to 65536 or equal to 1`. A migration tool replaying
> a vanilla PostgreSQL dump (where `CACHE` defaults to 1) will fail at the first `IDENTITY`
> column. Always include `(CACHE 1)` for strict ordering or `(CACHE 65536)` (or higher) for
> high-throughput workloads — see [scaling-guide.md](../auth/scaling-guide.md#choosing-identifier-types).

```sql
-- GENERATED ALWAYS: DSQL always generates the value; explicit inserts rejected unless OVERRIDING SYSTEM VALUE
CREATE TABLE users (
  id BIGINT GENERATED ALWAYS AS IDENTITY (CACHE 65536) PRIMARY KEY,
  name VARCHAR(255)
);

-- GENERATED BY DEFAULT: DSQL generates a value unless an explicit value is provided (closer to MySQL AUTO_INCREMENT behavior)
CREATE TABLE users (
  id BIGINT GENERATED BY DEFAULT AS IDENTITY (CACHE 65536) PRIMARY KEY,
  name VARCHAR(255)
);
```

#### Choosing a CACHE Size

**REQUIRED:** Specify CACHE explicitly. Supported values are `1` or `>= 65536`.

- **CACHE >= 65536** — High-frequency inserts, many concurrent sessions, tolerates gaps and ordering effects (e.g., IoT/telemetry, job IDs, order numbers)
- **CACHE = 1** — Low allocation rates, identifiers should follow allocation order closely, minimizing gaps matters more than throughput (e.g., account numbers, reference numbers)

### Option 3: Explicit SEQUENCE

Use a standalone sequence when multiple tables share a counter or when you need `nextval`/`setval` control.

```sql
-- Create the sequence (CACHE MUST be 1 or >= 65536)
CREATE SEQUENCE users_id_seq CACHE 65536 START 1;

-- Create table using the sequence
CREATE TABLE users (
  id BIGINT PRIMARY KEY DEFAULT nextval('users_id_seq'),
  name VARCHAR(255)
);
```

### Migrating Existing AUTO_INCREMENT Data

#### To UUID Primary Key

```sql
CREATE TABLE users_new (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  legacy_id INTEGER,  -- Preserve original AUTO_INCREMENT ID for reference
  name VARCHAR(255)
);

INSERT INTO users_new (id, legacy_id, name)
SELECT gen_random_uuid(), id, name
FROM users;
```

If other tables reference the old integer ID, update those references to use the new UUID or the `legacy_id` column.

#### To IDENTITY Column (Preserving Integer IDs)

```sql
-- Use GENERATED BY DEFAULT to allow explicit ID values during migration
CREATE TABLE users_new (
  id BIGINT GENERATED BY DEFAULT AS IDENTITY (CACHE 65536) PRIMARY KEY,
  name VARCHAR(255)
);

-- Migrate with original integer IDs preserved
INSERT INTO users_new (id, name)
SELECT id, name
FROM users;

-- Set the identity sequence to continue after the max existing ID
-- Get the max ID first:
SELECT MAX(id) as max_id FROM users_new;
-- Then reset the sequence (find the sequence name via:
--   SELECT pg_get_serial_sequence('users_new', 'id');):
SELECT setval('users_new_id_seq', (SELECT MAX(id) FROM users_new));
```

**Verify and swap** (see [Common Pattern](ddl-operations.md#common-verify--swap-pattern))
