AI Prompt Engineering

Getting ChatGPT to Write Accurate SQL Migrations Without Locking Production Tables

July 07, 2026 9 min read

You ask ChatGPT to write a migration that adds an index to a table with 50 million rows. It gives you a clean-looking CREATE INDEX statement. You run it on Friday afternoon and your production database grinds to a halt for eight minutes while every write queues up behind an exclusive table lock. That's the failure mode this article helps you avoid.

ChatGPT doesn't know your database engine, your table size, or your traffic patterns unless you tell it. Its default output is textbook-correct SQL β€” which often means it uses the simplest DDL syntax, not the production-safe one. With the right prompting strategy, you can get it to write migrations that are genuinely safe to run during business hours.

What you'll learn

  • Why ChatGPT defaults to locking DDL and what it's actually missing from context
  • Which DDL operations cause full table locks in PostgreSQL and MySQL
  • Prompt templates for non-blocking index creation, safe column additions, and constraint changes
  • How to ask ChatGPT to annotate locking risk directly in the migration output
  • Common gotchas that slip through even well-prompted migrations

Prerequisites

You should be comfortable writing raw SQL and understand the basics of database migration tools (Flyway, Liquibase, Alembic, or similar). The examples here use PostgreSQL syntax, but the prompting principles apply to MySQL and MariaDB as well. Table sizes and traffic patterns in the examples are illustrative; test every migration against your own dataset in staging.

Why ChatGPT defaults to table-locking DDL

Large language models are trained on documentation, tutorials, and Stack Overflow answers. The vast majority of that content is written for correctness, not for production scale. When a PostgreSQL tutorial shows you how to add an index, it writes CREATE INDEX, not CREATE INDEX CONCURRENTLY, because the latter comes with caveats that would distract from the point being taught.

ChatGPT inherits this bias. It produces the canonical form of each DDL statement. That's not wrong β€” it's just incomplete for your use case. The model doesn't know whether your table has 500 rows or 500 million. It doesn't know your replication topology, your deployment window, or whether your app retries failed writes. You have to inject that context explicitly.

There's a second issue: ChatGPT tends to write migrations as a single transaction. Some safe migration techniques deliberately cannot run inside a transaction (notably CREATE INDEX CONCURRENTLY in PostgreSQL). If the model wraps everything in BEGIN; ... COMMIT;, you'll get an error the first time you run it β€” and a confused junior engineer who reverts to the locking version because at least it ran.

The locking risk by database engine

The specific operations that cause full table locks differ between engines. Knowing this helps you write better prompts.

OperationPostgreSQLMySQL / InnoDB
Add a non-nullable column (no default)Full rewrite pre-11, metadata-only in 11+Full table copy
Add an indexExclusive lock (use CONCURRENTLY)Online DDL available (ALGORITHM=INPLACE)
Add a CHECK constraintFull scan + lock (use NOT VALID first)Full scan + lock
Add a foreign keyShareRowExclusive lock on both tablesShared lock during validation
Change column typeFull rewriteFull table copy
Rename a columnMetadata-only in 10+Full table copy pre-8.0, instant in 8.0+

When you prompt ChatGPT, include your engine version in the system context. The safe approach in PostgreSQL 15 can be very different from MySQL 5.7. A model that doesn't know which it's targeting will hedge toward the simplest syntax.

How to prompt ChatGPT for safe migrations

The single most effective change you can make is to front-load your constraints as a system-level instruction or as the opening paragraph of your prompt. Don't ask for a migration and then hope the model considers production safety. State it as a hard requirement before describing the change.

Here's a base prompt template to build from:

You are a database engineer writing SQL migrations for a PostgreSQL 15 production database.
Rules you must follow:
1. Never use DDL that takes an exclusive table lock on a table larger than ~1 million rows.
2. Use CREATE INDEX CONCURRENTLY for all new indexes.
3. Do not wrap operations that cannot run in a transaction (e.g., CREATE INDEX CONCURRENTLY) inside BEGIN/COMMIT.
4. When a safe migration requires multiple steps, list each step separately with a comment explaining the locking behavior.
5. For each migration step, add a SQL comment that rates the lock risk: LOW, MEDIUM, or HIGH.

Task: [describe your migration here]
Table name: [table]
Approximate row count: [N]
Deployment window: [zero-downtime required / maintenance window available]

Notice three things: you've given it a persona with operational responsibilities, you've given it explicit rules rather than vague hints, and you've supplied the context it needs to make risk decisions. This dramatically reduces hallucinated lock-safe claims.

For teams using database migration rollback scripts, you can extend this template to also request a rollback step for each forward migration β€” another area where ChatGPT often omits the safety detail unless asked.

Prompting for non-blocking index creation

This is the most common migration mistake. Here's what the default output looks like versus what you should get after prompting correctly.

Default (unsafe on large tables):

-- Add index on orders.customer_id
CREATE INDEX idx_orders_customer_id ON orders (customer_id);

After applying the base prompt template:

-- Step 1: Create index without blocking writes
-- Lock risk: LOW (CONCURRENTLY avoids exclusive lock; takes longer but safe for production)
-- NOTE: This statement cannot run inside a transaction block.
CREATE INDEX CONCURRENTLY idx_orders_customer_id ON orders (customer_id);

-- Verify the index was created successfully before proceeding:
-- SELECT indexname, indisvalid FROM pg_indexes
-- JOIN pg_index ON pg_indexes.indexname::text = pg_indexes.indexname
-- WHERE tablename = 'orders';

The second version includes the concurrency flag, the transaction warning, and a verification query. You got all of that because the prompt made the safety requirements explicit. If ChatGPT still drops the CONCURRENTLY keyword, follow up with:

Your migration still blocks writes.

Rewrite it assuming the table contains
250 million rows and serves
2,000 writes per second.

Every operation must be zero-downtime.

The additional operational context usually pushes the model toward a much safer solution.

Safe Column Additions

Adding a nullable column is generally inexpensive.

Example:

ALTER TABLE users
ADD COLUMN last_seen_at TIMESTAMP;

The trouble begins when developers combine:

  • NOT NULL
  • DEFAULT
  • Large tables

into a single migration.

Instead of asking:

Add a required column.

Prompt:

Generate a zero-downtime migration
for adding a required column
to a 100-million-row PostgreSQL table.

Avoid long table rewrites.

Explain every step.

A better migration becomes:

-- Step 1
ALTER TABLE users
ADD COLUMN last_seen_at TIMESTAMP;

-- Step 2
Backfill existing rows in batches.

-- Step 3
Add NOT NULL after verification.

The phased approach dramatically reduces locking.

Backfill Should Never Happen in One UPDATE

A common AI-generated migration:

UPDATE users
SET last_seen_at = NOW();

Looks innocent.

On a large table:

  • Long-running transaction
  • Massive WAL generation
  • Replication lag
  • Table bloat

Prompt instead:

Backfill existing rows
in batches of 5,000.

Assume production traffic
continues during migration.

The generated solution is much more likely to include pagination or batch updates.

Constraint Validation Can Be Deferred

Suppose you need:

CHECK (price >= 0)

Many developers generate:

ALTER TABLE products
ADD CONSTRAINT positive_price
CHECK (price >= 0);

On large PostgreSQL tables this immediately validates every row.

A safer pattern:

ALTER TABLE products
ADD CONSTRAINT positive_price
CHECK (price >= 0)
NOT VALID;

Later:

ALTER TABLE products
VALIDATE CONSTRAINT positive_price;

Prompt:

Prefer NOT VALID constraints
followed by separate validation
when supported.

This frequently reduces blocking significantly.

Foreign Keys Need Care Too

Another common migration:

ALTER TABLE orders
ADD CONSTRAINT fk_customer
FOREIGN KEY (customer_id)
REFERENCES customers(id);

Large tables may spend considerable time validating existing rows.

Prompt:

Generate a production-safe
foreign key migration.

Separate creation
from validation
where possible.

The resulting migration becomes much safer.

Changing Column Types

One of the highest-risk operations.

Example:

ALTER TABLE users
ALTER COLUMN age TYPE BIGINT;

Depending on:

  • Engine
  • Version
  • Existing data

this may rewrite the entire table.

Prompt:

Assume changing the column type
requires a full table rewrite.

Design a zero-downtime alternative.

Possible output:

  • New column
  • Dual writes
  • Background backfill
  • Application switch
  • Old column removal

This pattern is far safer than direct alteration.

Prompt for Lock Analysis

One prompt dramatically improves migration quality:

For every SQL statement,
explain:

- Lock type
- Lock duration
- Blocking risk
- Replication impact

The explanations often expose unsafe operations before deployment.

Ask for Rollback Plans

Many generated migrations have:

Forward migration

only.

Production requires rollback.

Prompt:

Generate:

Forward migration

Rollback migration

Verification queries

Explain rollback limitations.

Rollback scripts are especially valuable during staged deployments.

Verification Queries Matter

Never stop after DDL.

Prompt:

After every migration,
generate verification SQL
confirming:

- Constraint validity
- Index usability
- Row counts
- Expected metadata

Example:

SELECT *
FROM pg_indexes
WHERE tablename = 'orders';

or:

SELECT conname,
convalidated
FROM pg_constraint
WHERE conrelid = 'orders'::regclass;

Verification should be part of every migration.

PostgreSQL-Specific Prompt Improvements

Instead of:

Write a PostgreSQL migration.

Use:

PostgreSQL 15

200 million rows

Zero downtime

Streaming replication enabled

No maintenance window

Minimize blocking locks

Annotate every statement
with lock risk.

Providing operational constraints consistently produces safer SQL.

MySQL Prompt Improvements

Likewise:

MySQL 8.0

InnoDB

Large production table

Use online DDL

Prefer ALGORITHM=INPLACE
or ALGORITHM=INSTANT
when supported.

Avoid table copies.

The model cannot optimize for features it doesn't know are available.

Test the Migration Before Trusting It

Ask ChatGPT:

Generate tests for:

- Migration runtime

- Lock duration

- Replication lag

- Rollback

- Failure halfway through

- Interrupted deployment

These tests often reveal operational risks earlier than staging alone.

Common AI-Generated Migration Mistakes

Transaction Wrapping

Some operations cannot execute inside transactions.


Blocking Index Creation

Missing:

CONCURRENTLY

Large Single UPDATE

Creates long-running transactions.


Immediate Constraint Validation

Causes unnecessary blocking.


Missing Rollback

Forward-only migrations increase deployment risk.


No Verification Queries

Deployment succeeds without proving correctness.


No Version Awareness

Database version differences matter enormously.

A Production Prompt Template

A reusable prompt:

Act as a senior database reliability engineer.

Target:

PostgreSQL 15

Rules:

- Zero downtime

- Large production tables

- Streaming replication enabled

- Avoid exclusive locks

- Annotate lock risk

- Generate rollback

- Generate verification SQL

- Explain every operational trade-off

Task:

[Describe migration]

Table:

[Table Name]

Approximate rows:

[Count]

This prompt consistently produces much safer migrations than requesting SQL alone.

Review the Generated SQL Like a DBA

Before approving any migration, ask ChatGPT one final question:

Review this migration
as if you were
the primary database administrator.

Identify:

- Blocking operations

- Replication risks

- Lock escalation

- Long-running transactions

- Rollback concerns

Recommend safer alternatives.

Treat this as a mandatory second review.

Final Thoughts

SQL migrations that look perfectly correct in development can become major production incidents when they encounter large tables, continuous write traffic, replication, and strict availability requirements. ChatGPT doesn't intentionally generate unsafe migrationsβ€”it simply defaults to the canonical SQL syntax because it has no knowledge of your database size, engine version, deployment window, or operational constraints unless you provide them.

The key is to shift your prompts from "write this migration" to "design a production-safe migration under these conditions." Specify your database engine and version, approximate row counts, zero-downtime requirements, acceptable lock levels, replication setup, and rollback expectations before requesting any SQL. Then require the model to explain locking behavior, generate verification queries, and review its own output for operational risk.

Used this way, ChatGPT becomes much more than a SQL generator. It becomes a valuable assistant for designing safer migration plansβ€”while the final responsibility for testing, staging, and deployment remains exactly where it belongs: with the engineering team.

Frequently Asked Questions

How do I add an index to a large PostgreSQL table without locking it?

Use CREATE INDEX CONCURRENTLY instead of plain CREATE INDEX. This builds the index in the background without taking an exclusive table lock, allowing reads and writes to continue. Note that it cannot be run inside a transaction block.

Why does ChatGPT generate SQL migrations that lock production tables?

ChatGPT is trained on documentation and tutorials that favor syntactic simplicity over operational safety. It doesn't know your table sizes or traffic patterns unless you tell it, so it defaults to the simplest DDL form, which often takes exclusive locks. Adding explicit rules and context to your prompt reliably fixes this.

What is the expand-contract pattern for database migrations?

The expand-contract pattern splits a breaking schema change into multiple deployments: first add the new structure alongside the old (expand), update application code to use both, then remove the old structure once all traffic has migrated (contract). It avoids table locks and allows zero-downtime rollouts.

Can I add a NOT NULL column to a large table without downtime in PostgreSQL?

Yes, in PostgreSQL 11 and later you can add a NOT NULL column with a constant default in a metadata-only operation that doesn't rewrite the table. For non-constant defaults or older versions, the safe approach is to add a nullable column first, backfill it in batches, then add a CHECK NOT NULL constraint using NOT VALID before validating separately.

What happens if CREATE INDEX CONCURRENTLY fails partway through in PostgreSQL?

PostgreSQL leaves behind an invalid index that still occupies a name in the catalog. Subsequent attempts to create an index with the same name will fail with a duplicate error. You need to DROP the invalid index first using DROP INDEX CONCURRENTLY, then retry the creation.

πŸ“€ Share this article

Sign in to save

Comments (0)

No comments yet. Be the first!

Leave a Comment

Sign in to comment with your profile.

πŸ“¬ Weekly Newsletter

Stay ahead of the curve

Get the best programming tutorials, data analytics tips, and tool reviews delivered to your inbox every week.

No spam. Unsubscribe anytime.