Two Postgres databases are enough to see it work. The checksum is built in SQL, so only the digests and the rows that differ leave the server.
DB Diff
Know when your data drifts
Supported engines
Try it yourself:
The MySQL dialect has to work around unsigned integers and charset decoding in SQL. The result is the same checksum, byte for byte, as the other two.
Try it yourself:
ClickHouse folds with groupBitXor and has to reverse the byte order of the digest. It still lands on the same value the golden test pins.
Try it yourself:
Use cases
Compare the columns that matter. Excluding the noisy ones drops the rows whose only difference lives there, and the checksum reads less data while it does it.
Try it yourself:
A cross engine pair is refused before it connects. Engines render values their own way, so guessing would report every row as changed.
Try it yourself:
Get started
DB Diff is built for the case a dump and compare cannot handle: a table with millions of rows where you need to know which rows drifted, without transferring the table.
Three engines
PostgreSQL, MySQL and ClickHouse, with one checksum contract across all three
Checksums only
Server-side MD5 chunking, so only digests cross the wire when the tables match
Pinpoint drift
A differing segment is read row by row and reported as primary keys
Column control
Include or exclude columns to leave the noisy ones out of the comparison
CI ready
Exit 0 when the tables match, 1 when they differ, 2 when the run failed
Tunable segments
Trade query count against how much is read when something differs
Install in one line
Requires Go 1.24+ or Homebrew.
Requirements and limits
Engines: PostgreSQL 14 or newer, MySQL and ClickHouse. Both databases must run the same engine; cross engine comparison is DB Diff Pro.
Table: one integer primary key named id, with the same table name and column order
on both sides.
Run: a differing segment is read whole, and the comparison is not a transaction. Pin the session timezone and charset on both connections.
License: MIT. The releases page has the current version and checksums.
Algorithm notes
DB Diff does the comparison inside the database. The wire carries checksums when the tables match, and rows only when they do not.
-
01
Segment the table
The table is cut into ranges of primary keys, 10,000 by default. Both sides can address the same range without moving a row.
-
02
Checksum each segment
Each side hashes every compared column on its own, folds the digests with XOR, and returns one 64-bit checksum. Equal checksums skip the segment.
-
03
Read only what differs
A differing segment is read row by row and compared column by column. The output is the primary keys that changed.
md5( null_bitmap || md5(COALESCE(col_a, '')) || md5(COALESCE(col_b, '')) || ... ) -> first 8 bytes
segment = XOR(row checksums)
The comparison runs within one engine. Engines render values their own way: a boolean is true in
PostgreSQL and 1 in MySQL, so a cross engine run is refused rather than reported as every row
changed. DB Diff Pro adds the canonical form that makes it possible.
DB Diff Pro
Need to compare across engines? DB Diff Pro adds cross engine support to DB Diff. A wall clock reading is kept distinct from an instant, and floats are compared bit for bit rather than by how the engine happens to print them.