Schema diff
Paste two SQL schemas (PostgreSQL or MySQL, hand-written or from pg_dump / mysqldump) and get the migration from one to the other as SQL, Flyway versioned files or a Liquibase changelog (YAML or XML), with warnings for the changes that rewrite or lock tables — by database version — and an ER diagram of what changed.
Added
Works with
- Postgres playground Databases and streamingTry it in the Postgres playground builds schema A there and runs the migration on it
- Diagram and AsciiDoc studio Formatting and referenceOpen in Diagrams sends the ER diagram there as Mermaid
How to use it
You have the schema the database has now and the one you want. This page works out the ALTER
statements between them, puts them in an order that runs (views dropped before the columns they
read, foreign keys after the keys they point at, enum values before they are used), and tells you
which of them will hurt on a big table in production.
How to use it
-
Paste the current schema into A and the wanted one into B — or Open file. Both can be hand-written DDL or a schema-only dump (
pg_dump --schema-only,mysqldump --no-data). The dialect is detected; choose it if the guess is wrong. -
Pick the server version. It changes the advice: adding a column with a default rewrites the table before PostgreSQL 11 and not after,
SET NOT NULLcan skip its scan from 12,ALTER TYPE … ADD VALUEcan run in a transaction from 12, a generated column’s expression can be changed from 17, and MySQL 8.0 before 8.0.29 adds columns instantly only at the end. -
Online patterns (PostgreSQL, on by default) writes the migration the way you would for a busy table:
CREATE INDEX CONCURRENTLY; foreign keys and checks addedNOT VALIDthen validated (which does not block writes);NOT NULLthrough a validatedCHECK (col IS NOT NULL); unique and primary keys built as a concurrent unique index and attached withUSING INDEX. Turn it off for the plain version. lock_timeout is put at the top so a migration that cannot get its lock fails quickly instead of queueing every other query behind it. -
Read the warnings. Risks lose data or rewrite a table under an exclusive lock; warnings block writes or scan the table; notes explain why something that looks risky is not.
-
Take the migration as plain SQL, as Flyway files (
V2__description.sql; statements that cannot run in a transaction get their own versioned file, because Flyway runs each file in one transaction), or as a Liquibase changelog in YAML or XML (native change types where Liquibase has one —dropColumn,dropTable,addNotNullConstraint,dropDefaultValue,addForeignKeyConstraintwithvalidate: falseand so on — andsqlchanges for the rest; concurrent index builds getrunInTransaction: false). Set the version, description and author above the output. -
ER diagram draws B with what changed marked, or A or B alone. Download it as SVG, copy it as Mermaid, or Open in Diagrams to edit and export it in the Diagrams tool.
-
With PostgreSQL, Try it in the Postgres playground opens the Postgres playground with schema A and the migration ready to run in real PostgreSQL 18 in your browser.
What it compares
Tables; columns with their type, NOT NULL, default, identity / serial / AUTO_INCREMENT,
generated expression (and, in MySQL, ON UPDATE and comments); primary keys, unique, check and
foreign-key constraints (with ON DELETE / ON UPDATE); indexes, including unique, partial
(WHERE), expression, INCLUDE and non-btree ones; PostgreSQL enum types; views and
materialized views. Unnamed constraints and indexes get the names PostgreSQL would give them
(orders_customer_id_fkey, orders_customer_id_idx) or MySQL would (orders_ibfk_1), so a
hand-written script and a dump of the same database compare equal. Types are compared by meaning
(int4 = integer, character varying(20) = varchar(20)), defaults and conditions without
casts and redundant parentheses ('new'::order_status = 'new'), and view bodies after
removing what pg_dump adds when it re-prints them. Functions, triggers, sequences' own options,
grants, partitions and comments are not compared; the statements skipped are listed under the
output.
Renames cannot be told apart from a drop and an add, so the migration drops and adds — and warns
when a dropped and an added column (or table) look like the same thing, with the RENAME to use
instead.
The warnings, in short
-
Table rewrites (everything blocked until done):
ALTER COLUMN … TYPEunless the types are binary-compatible (a longervarchar,varchar→text, morenumericprecision); adding a column with a volatile default (gen_random_uuid(),random(),clock_timestamp(),nextval), an identity or a stored generated column; any default before PostgreSQL 11; recreating an enum to remove or reorder values. In MySQL:MODIFYwith a new type (except enum values added at the end, andvarchargrowth that keeps its length-prefix size). -
Locks and scans:
CREATE INDEXwithoutCONCURRENTLYblocks writes;ADD FOREIGN KEY,ADD CHECKandSET NOT NULLscan the table while holding a lock;DROP INDEXwaits for every query on the table. -
Data loss: dropped tables and columns. Deploy code that no longer uses a column before dropping it.
-
Dependencies: views that read a column being dropped or retyped are dropped and recreated around the change; foreign keys without an index on the referencing side.
How it was checked
The PostgreSQL migrations are run against PostgreSQL itself in the tests: schema A is created in
PGlite (PostgreSQL 18) with some rows, the migration runs statement by statement (the
CONCURRENTLY ones outside a transaction), and the catalog — columns, types, nullability,
defaults, identity, constraints, indexes, enums, views — must come out identical to a database
built from B, with the rows still there. That is done for the examples in both directions, with
and without online patterns, and from pg_dump output. The MySQL examples were applied to MySQL
8.4 and mysqldump of the result compared with B’s.
Related: Explain plan (what the new index does to your queries), Postgres playground, Diagrams, SQL formatter, DynamoDB and MongoDB playgrounds.
Feedback is a gift. Something to improve, a feature to add, or a bug? Tell me.
Runs entirely in your browser; nothing you type leaves this page.