go-data-checksum is a high-performance data check tool to verify data integrity between MySQL databases/tables. go-data-checksum supports full data check via primary key and incremental data check via specified time field; supports full field check or specified field check also.
- Record-level Analysis: Shows exactly which records differ by primary key
- Three Types of Differences:
- (minus): Records that exist only in the source table+ (plus): Records that exist only in the target table~ (tilde): Records that exist in both but have different data
- Sample Output: Shows actual primary key values for differing records
- Performance Optimized: Processes data in chunks to handle large tables
The tool automatically detects and uses the best unique key for data chunking:
- Prioritizes PRIMARY KEY when available
- Falls back to other unique indexes
- Prefers non-nullable keys
- Optimizes for integer data types
- Handles composite keys intelligently
make build # builds bin/go-data-checksum and bin/go-data-sync
make test # runs the unit tests
Or build the binaries directly:
go build -o bin/go-data-checksum cmd/checksum/main.go
go build -o bin/go-data-sync cmd/sync/main.go
# Usage help
./bin/go-data-checksum --help
-check-column-names string
Column names to check,eg: col1,col2,col3. By default, all columns are used.
-chunk-size int
amount of rows to handle in each iteration (allowed range: 10-100,000) (default 1000)
-conn-db-timeout int
connect db timeout (default 60)
-debug
debug mode (very verbose)
-default-retries int
Default number of retries for various operations before panicking (default 10)
-enable-differential-reporting
Enable detailed differential reporting showing which records differ by primary key (default false)
-generate-sync-sql
Generate REPLACE INTO statements for synchronizing differences to a file
-ignore-row-count-check
Shall we ignore check by counting rows? Default: false
-is-superset-as-equal
Shall we think that the records in target table is the superset of the source as equal? By default, we think the records are exactly equal as equal.
-logfile string
Log file name.
-max-display-differences int
Maximum number of differences to display in output (default: 10) (default 10)
-max-sample-differences int
Maximum number of sample differences to collect during analysis (default: 100) (default 100)
-source-db-host string
Source MySQL hostname (default "127.0.0.1")
-source-db-name string
Source database list separated by comma, eg: db1 or db1,db2.
-source-db-password string
MySQL password
-source-db-port int
Source MySQL port (default 3306)
-source-db-user string
MySQL user
-source-table-name string
Source tables list separated by comma, eg: table1 or table1,table2.
-source-table-regexp string
Source table names regular expression, eg: 'test_[0-9][0-9]\\.test_20.*'
-specified-time-begin string
Specified begin time of time column to check.
-specified-time-column string
Specified time column for range dataCheck.
-specified-time-end string
Specified end time of time column to check.
-sync-sql-file string
Output file for sync SQL statements (default: stdout if not specified)
-target-database-add-suffix string
Target database name add a suffix to the source database name.
-target-database-as-source
Is target database name as source? default: true. (default true)
-target-db-host string
Target MySQL hostname (default "127.0.0.1")
-target-db-name string
Target database list separated by comma, eg: db1 or db1,db2.
-target-db-password string
MySQL password
-target-db-port int
Target MySQL port (default 3306)
-target-db-user string
MySQL user
-target-table-add-suffix string
Target table name add a suffix to the source table name.
-target-table-as-source
Is target table name as source? default: true. (default true)
-target-table-name string
Target tables list separated by comma, eg: table1 or table1,table2.
-threads int
Parallel threads of table checksum. (default 1)
-time-range-per-step duration
time range per step for specified time column check,default 5m,eg:1h/2m/3s/4ms (default 5m0s)
-version
Print version & exit
# Checks every table in app_db on the source against the same-named tables
# on the target. Fails fast per table on a row-count mismatch, then verifies
# contents chunk by chunk with CRC32.
./bin/go-data-checksum \
--source-db-host="prod.example.com" --source-db-port=3306 \
--source-db-user="checker" --source-db-password="xxxx" \
--target-db-host="replica.example.com" --target-db-port=3306 \
--target-db-user="checker" --target-db-password="xxxx" \
--source-db-name="app_db" \
--threads=4Exit summary: All N pairs of tables check result is equal. or
Table records check result X equal, Y not equal.
# --enable-differential-reporting rescans any unequal table and reports
# per-record differences by primary key:
# - record exists only on the SOURCE (missing on the replica)
# + record exists only on the TARGET (extra on the replica)
# ~ record exists on both but differs (modified on the replica)
./bin/go-data-checksum \
--source-db-host="192.168.50.75" --source-db-port=3306 \
--source-db-user="root" --source-db-password="xxxx" \
--target-db-host="192.168.50.75" --target-db-port=3307 \
--target-db-user="root" --target-db-password="xxxx" \
--source-db-name="sbtest" \
--enable-differential-reporting \
--max-display-differences=25 \
--logfile="comparison.log"# Step 1 — find differences and write REPLACE INTO statements to a file.
# Raise --max-sample-differences to cover every difference; the tool warns
# (in the log and inside the file) if the file is incomplete.
./bin/go-data-checksum \
--source-db-host="prod.example.com" --source-db-user="checker" --source-db-password="xxxx" \
--target-db-host="replica.example.com" --target-db-user="checker" --target-db-password="xxxx" \
--source-db-name="app_db" \
--source-table-name="orders" \
--enable-differential-reporting \
--generate-sync-sql \
--sync-sql-file="sync_orders.sql" \
--max-sample-differences=10000
# Step 2 — review what would be applied (go-data-sync is dry-run by default)
./bin/go-data-sync --sql-file="sync_orders.sql" \
--target-db-host="replica.example.com" --target-db-user="admin" --target-db-password="xxxx"
# Step 3 — apply in transactional batches
./bin/go-data-sync --sql-file="sync_orders.sql" \
--target-db-host="replica.example.com" --target-db-user="admin" --target-db-password="xxxx" \
--execute
# Step 4 — re-verify: the table pair should now report equal
./bin/go-data-checksum \
--source-db-host="prod.example.com" --source-db-user="checker" --source-db-password="xxxx" \
--target-db-host="replica.example.com" --target-db-user="checker" --target-db-password="xxxx" \
--source-db-name="app_db" --source-table-name="orders" \
--enable-differential-reportingNote: target-only records (+) are never included in sync SQL — removing
rows from the target requires a manual, reviewed DELETE.
# Check every table matching sbtest.sbtest* across instances
./bin/go-data-checksum \
--source-db-host="1.1.1.1" --source-db-port=3307 --source-db-user="test" --source-db-password="xxxx" \
--target-db-host="8.8.8.8" --target-db-port=3306 --target-db-user="test" --target-db-password="xxxx" \
--source-table-regexp="sbtest\.sbtest.*" \
--threads=4# Only verify rows whose updated_at falls inside the window, walking it in
# 30-minute steps. Ideal for hourly/daily incremental verification jobs
# where a full-table pass is too expensive.
./bin/go-data-checksum \
--source-db-host="prod.example.com" --source-db-user="checker" --source-db-password="xxxx" \
--target-db-host="replica.example.com" --target-db-user="checker" --target-db-password="xxxx" \
--source-db-name="app_db" \
--source-table-name="events" \
--specified-time-column="updated_at" \
--specified-time-begin="2026-07-04 00:00:00" \
--specified-time-end="2026-07-05 00:00:00" \
--time-range-per-step=30m \
--enable-differential-reporting# Source app_db.users vs target app_db_new.users_v2 — explicit mapping
./bin/go-data-checksum \
... connection flags ... \
--source-db-name="app_db" --source-table-name="users" \
--target-db-name="app_db_new" --target-table-name="users_v2"
# Or by suffix: app_db.users vs app_db.users_new for every source table
./bin/go-data-checksum \
... connection flags ... \
--source-db-name="app_db" \
--target-table-add-suffix="_new"# Compare only the business columns (skip audit/metadata columns), and
# accept the target having extra rows (e.g. target keeps soft-deleted rows).
# Note: sync SQL still always writes FULL rows, regardless of this flag.
./bin/go-data-checksum \
... connection flags ... \
--source-db-name="app_db" --source-table-name="accounts" \
--check-column-names="id,email,balance,status" \
--is-superset-as-equal \
--ignore-row-count-check=== DIFFERENTIAL ANALYSIS RESULTS ===
Table Pair: dba.users => dba.users
- 128799 records exist only in SOURCE
+ 135 records exist only in TARGET
~ 45 records have different data
= 1234567 records are identical
=== SAMPLE DIFFERENCES ===
- Record (id=123, tenant_id=456) exists only in source
- Record (id=789, tenant_id=999) exists only in source
+ Record (id=555, tenant_id=456) exists only in target
+ Record (id=666, tenant_id=789) exists only in target
~ Record (id=999, tenant_id=456) modified: source_checksum=abc123, target_checksum=def456
~ Record (id=111, tenant_id=789) modified: source_checksum=xyz789, target_checksum=qwe456
=== END DIFFERENTIAL ANALYSIS ===
Notes:
- The differential analysis always rescans the whole table pair, so the counts cover every record — including target-only records whose keys fall outside the source table's key range.
- The analysis runs whenever a table pair is found unequal, including when the row-count pre-check already shows a mismatch.
-(minus): Records that exist in the source database but are missing in the target+(plus): Records that exist in the target database but are missing in the source~(tilde): Records that exist in both databases but have different data (checksum mismatch)=(equals): Records that are identical in both databases
Now you can use differential reporting by adding --enable-differential-reporting to your existing command!
The --generate-sync-sql feature generates REPLACE INTO statements for synchronizing table differences, inspired by Percona's pt-table-sync tool. This allows you to:
- Identify differences between source and target tables
- Generate SQL statements to fix those differences
- Apply the changes to bring tables into sync
REPLACE INTO Behavior:
- If a row with the same unique key doesn't exist in target → INSERT the row
- If a row with the same unique key exists in target → DELETE old row and INSERT new row
- Atomic operation that handles both insert and update scenarios
What Gets Synchronized:
- Source-only records (
-): Generated as REPLACE INTO to add missing rows - Modified records (
~): Generated as REPLACE INTO to update changed data - Target-only records (
+): NOT generated (safety: requires explicit DELETE)
-- Sync SQL for source_db.users => target_db.users
-- Generated at: 2025-06-06 14:30:22
-- Total differences: source_only=5, target_only=0, modified=2
REPLACE INTO `target_db`.`users` (`id`, `name`, `email`, `created_at`) VALUES (123, 'John Doe', 'john@example.com', '2025-01-15 10:00:00');
REPLACE INTO `target_db`.`users` (`id`, `name`, `email`, `created_at`) VALUES (456, 'Jane Smith', 'jane@example.com', '2025-02-20 15:30:00');
REPLACE INTO `target_db`.`users` (`id`, `name`, `email`, `created_at`) VALUES (789, 'Bob Wilson', 'bob@example.com', NULL);
-- Total REPLACE INTO statements generated: 3- SQL Escaping: Single quotes, backslashes, and control characters are
properly escaped (
'→'',\→\\, newlines →\n) - NULL Handling: NULL values are written as
NULL(not quoted) - Datetime Handling: DATETIME/TIMESTAMP values are written as proper MySQL
literals (
'2026-01-05 14:00:00') - Full-Row Statements: sync SQL always covers every column of the table,
even when
--check-column-namesrestricted the checksum to a subset — REPLACE INTO deletes and re-inserts the row, so partial statements would silently reset unlisted columns - Atomic Operations: REPLACE INTO is atomic within InnoDB
- Sample Limits: Use
--max-sample-differencesto control statement count; if the file does not cover every difference found, a warning is logged and written into the file - Fresh Output: the sync SQL file is truncated at the start of each run so it never contains stale statements from a previous run
- Review Before Applying: Always review generated SQL before execution —
go-data-syncruns in dry-run mode by default for exactly this reason - Test on Staging: Test sync SQL on non-production environments first
- Backup Target: Take a backup of target database before applying changes
- Handle Target-Only: Manually review and handle target-only records — they are never included in the generated SQL
- Apply with go-data-sync: it validates that the file contains only REPLACE INTO statements and applies them in transactional batches (see "COMPANION CLI: go-data-sync" below and EXAMPLES #3 for the full find → review → sync → re-verify workflow)
# Enhanced test command with differential reporting
./bin/go-data-checksum \
--source-db-host="1.1.1.1" \
--source-db-port=3307 \
--source-db-user="test" \
--source-db-password="xxxx" \
--target-db-host="8.8.8.8" \
--target-db-port=3306 \
--target-db-user="test" \
--target-db-password="xxxx" \
--source-table-regexp="test\.sbtest.*" \
--ignore-row-count-check \
--is-superset-as-equal \
--enable-differential-reporting \
--threads=4go-data-sync (built alongside the checksum binary by make build) applies a
sync SQL file generated with --generate-sync-sql to the target instance.
-sql-file string
Sync SQL file generated by go-data-checksum --generate-sync-sql (required)
-target-db-host string
Target MySQL hostname (default "127.0.0.1")
-target-db-port int
Target MySQL port (default 3306)
-target-db-user string
Target MySQL user
-target-db-password string
Target MySQL password
-execute
Actually apply the statements. Without this flag the tool runs in dry-run mode.
-batch-size int
Number of statements per transaction (default 100)
-conn-db-timeout int
connect db timeout in seconds (default 60)
-version
Print version & exit
Safety model:
- Dry-run by default — shows a per-table statement summary and a preview;
nothing is executed until you pass
--execute. - REPLACE INTO only — any other statement in the file aborts the run before anything is executed.
- Transactional batches — a failed batch is rolled back and the run stops, reporting the exact line number of the failing statement.
- Reports rows inserted (missing on target) vs replaced (modified on target).
# 1. Find differences and generate sync SQL
./bin/go-data-checksum ... --enable-differential-reporting \
--generate-sync-sql --sync-sql-file=sync_orders.sql --max-sample-differences=10000
# 2. Review, then dry-run
./bin/go-data-sync --sql-file=sync_orders.sql \
--target-db-host=replica.example.com --target-db-user=admin --target-db-password=xxx
# 3. Apply
./bin/go-data-sync --sql-file=sync_orders.sql \
--target-db-host=replica.example.com --target-db-user=admin --target-db-password=xxx \
--execute
# 4. Re-verify
./bin/go-data-checksum ... --enable-differential-reporting# First, find the middle range of IDs
mysql --defaults-group-suffix=_replica1 -e "SELECT MIN(id) as min_id, MAX(id) as max_id, COUNT(*) as total FROM sbtest.sbtest3"
# Delete 5 records from around the middle (assuming IDs are roughly 1-100000)
mysql --defaults-group-suffix=_replica1 -e "DELETE FROM sbtest.sbtest3 WHERE id BETWEEN 50000 AND 50004 LIMIT 5"
# Verify the deletion
mysql --defaults-group-suffix=_replica1 -e "SELECT COUNT(*) FROM sbtest.sbtest3"
mysql --defaults-group-suffix=_replica1 -e "SELECT id FROM sbtest.sbtest3 WHERE id BETWEEN 49995 AND 50010 ORDER BY id"
mysql --defaults-group-suffix=_replica1 -e "SELECT MIN(id) as min_id, MAX(id) as max_id, COUNT(*) as total FROM sbtest.sbtest3"
+--------+--------+--------+
| min_id | max_id | total |
+--------+--------+--------+
| 1 | 100000 | 100000 |
+--------+--------+--------+
mysql --defaults-group-suffix=_replica1 -e "DELETE FROM sbtest.sbtest3 WHERE id BETWEEN 50000 AND 50004 LIMIT 5"
mysql --defaults-group-suffix=_replica1 -e "SELECT COUNT(*) FROM sbtest.sbtest3"
+----------+
| COUNT(*) |
+----------+
| 99995 |
+----------+
mysql --defaults-group-suffix=_replica1 -e "SELECT id FROM sbtest.sbtest3 WHERE id BETWEEN 49995 AND 50010 ORDER BY id"
+-------+
| id |
+-------+
| 49995 |
| 49996 |
| 49997 |
| 49998 |
| 49999 |
| 50005 |
| 50006 |
| 50007 |
| 50008 |
| 50009 |
| 50010 |
+-------+./bin/go-data-checksum \
--source-db-host="192.168.50.75" \
--source-db-port=3306 \
--source-db-user="root" \
--source-db-password="s3cr3t" \
--target-db-host="192.168.50.75" \
--target-db-port=3307 \
--target-db-user="root" \
--target-db-password="s3cr3t" \
--source-db-name="sbtest" \
--default-retries=3 \
--enable-differential-reporting \
--ignore-row-count-check \
--logfile="comparison.log"
time="2025-06-06T11:39:39-07:00" level=info msg="Starting go-data-checksum dev..."
time="2025-06-06T11:39:39-07:00" level=info msg="24 pairs of source and target tables:"
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest1 => sbtest.sbtest1 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest10 => sbtest.sbtest10 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest11 => sbtest.sbtest11 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest12 => sbtest.sbtest12 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest13 => sbtest.sbtest13 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest14 => sbtest.sbtest14 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest15 => sbtest.sbtest15 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest16 => sbtest.sbtest16 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest17 => sbtest.sbtest17 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest18 => sbtest.sbtest18 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest19 => sbtest.sbtest19 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest2 => sbtest.sbtest2 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest20 => sbtest.sbtest20 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest21 => sbtest.sbtest21 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest22 => sbtest.sbtest22 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest23 => sbtest.sbtest23 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest24 => sbtest.sbtest24 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest3 => sbtest.sbtest3 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest4 => sbtest.sbtest4 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest5 => sbtest.sbtest5 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest6 => sbtest.sbtest6 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest7 => sbtest.sbtest7 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest8 => sbtest.sbtest8 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Table map: sbtest.sbtest9 => sbtest.sbtest9 ."
time="2025-06-06T11:39:39-07:00" level=info msg="Starting check table pair: sbtest.sbtest1 => sbtest.sbtest1 ."
time="2025-06-06T11:39:40-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest1 => sbtest.sbtest1 , tableCheckDuration=558.509111ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:40-07:00" level=info msg="End check table pair: sbtest.sbtest1 => sbtest.sbtest1 ."
time="2025-06-06T11:39:40-07:00" level=info msg="Starting check table pair: sbtest.sbtest10 => sbtest.sbtest10 ."
time="2025-06-06T11:39:41-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest10 => sbtest.sbtest10 , tableCheckDuration=1.072464832s, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:41-07:00" level=info msg="End check table pair: sbtest.sbtest10 => sbtest.sbtest10 ."
time="2025-06-06T11:39:41-07:00" level=info msg="Starting check table pair: sbtest.sbtest11 => sbtest.sbtest11 ."
time="2025-06-06T11:39:41-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest11 => sbtest.sbtest11 , tableCheckDuration=257.890423ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:41-07:00" level=info msg="End check table pair: sbtest.sbtest11 => sbtest.sbtest11 ."
time="2025-06-06T11:39:41-07:00" level=info msg="Starting check table pair: sbtest.sbtest12 => sbtest.sbtest12 ."
time="2025-06-06T11:39:41-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest12 => sbtest.sbtest12 , tableCheckDuration=253.987565ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:41-07:00" level=info msg="End check table pair: sbtest.sbtest12 => sbtest.sbtest12 ."
time="2025-06-06T11:39:41-07:00" level=info msg="Starting check table pair: sbtest.sbtest13 => sbtest.sbtest13 ."
time="2025-06-06T11:39:41-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest13 => sbtest.sbtest13 , tableCheckDuration=249.332772ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:41-07:00" level=info msg="End check table pair: sbtest.sbtest13 => sbtest.sbtest13 ."
time="2025-06-06T11:39:41-07:00" level=info msg="Starting check table pair: sbtest.sbtest14 => sbtest.sbtest14 ."
time="2025-06-06T11:39:42-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest14 => sbtest.sbtest14 , tableCheckDuration=275.567289ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:42-07:00" level=info msg="End check table pair: sbtest.sbtest14 => sbtest.sbtest14 ."
time="2025-06-06T11:39:42-07:00" level=info msg="Starting check table pair: sbtest.sbtest15 => sbtest.sbtest15 ."
time="2025-06-06T11:39:42-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest15 => sbtest.sbtest15 , tableCheckDuration=259.829183ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:42-07:00" level=info msg="End check table pair: sbtest.sbtest15 => sbtest.sbtest15 ."
time="2025-06-06T11:39:42-07:00" level=info msg="Starting check table pair: sbtest.sbtest16 => sbtest.sbtest16 ."
time="2025-06-06T11:39:42-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest16 => sbtest.sbtest16 , tableCheckDuration=251.448752ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:42-07:00" level=info msg="End check table pair: sbtest.sbtest16 => sbtest.sbtest16 ."
time="2025-06-06T11:39:42-07:00" level=info msg="Starting check table pair: sbtest.sbtest17 => sbtest.sbtest17 ."
time="2025-06-06T11:39:42-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest17 => sbtest.sbtest17 , tableCheckDuration=252.834589ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:42-07:00" level=info msg="End check table pair: sbtest.sbtest17 => sbtest.sbtest17 ."
time="2025-06-06T11:39:42-07:00" level=info msg="Starting check table pair: sbtest.sbtest18 => sbtest.sbtest18 ."
time="2025-06-06T11:39:43-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest18 => sbtest.sbtest18 , tableCheckDuration=254.886938ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:43-07:00" level=info msg="End check table pair: sbtest.sbtest18 => sbtest.sbtest18 ."
time="2025-06-06T11:39:43-07:00" level=info msg="Starting check table pair: sbtest.sbtest19 => sbtest.sbtest19 ."
time="2025-06-06T11:39:43-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest19 => sbtest.sbtest19 , tableCheckDuration=255.367792ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:43-07:00" level=info msg="End check table pair: sbtest.sbtest19 => sbtest.sbtest19 ."
time="2025-06-06T11:39:43-07:00" level=info msg="Starting check table pair: sbtest.sbtest2 => sbtest.sbtest2 ."
time="2025-06-06T11:39:43-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest2 => sbtest.sbtest2 , tableCheckDuration=254.412822ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:43-07:00" level=info msg="End check table pair: sbtest.sbtest2 => sbtest.sbtest2 ."
time="2025-06-06T11:39:43-07:00" level=info msg="Starting check table pair: sbtest.sbtest20 => sbtest.sbtest20 ."
time="2025-06-06T11:39:43-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest20 => sbtest.sbtest20 , tableCheckDuration=251.905263ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:43-07:00" level=info msg="End check table pair: sbtest.sbtest20 => sbtest.sbtest20 ."
time="2025-06-06T11:39:43-07:00" level=info msg="Starting check table pair: sbtest.sbtest21 => sbtest.sbtest21 ."
time="2025-06-06T11:39:44-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest21 => sbtest.sbtest21 , tableCheckDuration=294.660871ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:44-07:00" level=info msg="End check table pair: sbtest.sbtest21 => sbtest.sbtest21 ."
time="2025-06-06T11:39:44-07:00" level=info msg="Starting check table pair: sbtest.sbtest22 => sbtest.sbtest22 ."
time="2025-06-06T11:39:44-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest22 => sbtest.sbtest22 , tableCheckDuration=254.852414ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:44-07:00" level=info msg="End check table pair: sbtest.sbtest22 => sbtest.sbtest22 ."
time="2025-06-06T11:39:44-07:00" level=info msg="Starting check table pair: sbtest.sbtest23 => sbtest.sbtest23 ."
time="2025-06-06T11:39:44-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest23 => sbtest.sbtest23 , tableCheckDuration=255.28893ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:44-07:00" level=info msg="End check table pair: sbtest.sbtest23 => sbtest.sbtest23 ."
time="2025-06-06T11:39:44-07:00" level=info msg="Starting check table pair: sbtest.sbtest24 => sbtest.sbtest24 ."
time="2025-06-06T11:39:44-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest24 => sbtest.sbtest24 , tableCheckDuration=250.826814ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:44-07:00" level=info msg="End check table pair: sbtest.sbtest24 => sbtest.sbtest24 ."
time="2025-06-06T11:39:44-07:00" level=info msg="Starting check table pair: sbtest.sbtest3 => sbtest.sbtest3 ."
time="2025-06-06T11:39:47-07:00" level=error msg="Critical: record CRC32 checksum value is not equal of table pair: sbtest.sbtest3 => sbtest.sbtest3 , tableCheckDuration=2.133377276s"
time="2025-06-06T11:39:47-07:00" level=info msg="Running differential analysis for table pair: sbtest.sbtest3 => sbtest.sbtest3"
time="2025-06-06T11:39:47-07:00" level=info msg="Starting differential analysis for table pair: sbtest.sbtest3 => sbtest.sbtest3"
time="2025-06-06T11:39:47-07:00" level=info msg="=== DIFFERENTIAL ANALYSIS RESULTS ==="
time="2025-06-06T11:39:47-07:00" level=info msg="Table Pair: sbtest.sbtest3 => sbtest.sbtest3"
time="2025-06-06T11:39:47-07:00" level=error msg="- 5 records exist only in SOURCE"
time="2025-06-06T11:39:47-07:00" level=info msg="= 99995 records are identical"
time="2025-06-06T11:39:47-07:00" level=info msg="=== SAMPLE DIFFERENCES ==="
time="2025-06-06T11:39:47-07:00" level=error msg="- Record (id=50000) exists only in source"
time="2025-06-06T11:39:47-07:00" level=error msg="- Record (id=50001) exists only in source"
time="2025-06-06T11:39:47-07:00" level=error msg="- Record (id=50002) exists only in source"
time="2025-06-06T11:39:47-07:00" level=error msg="- Record (id=50003) exists only in source"
time="2025-06-06T11:39:47-07:00" level=error msg="- Record (id=50004) exists only in source"
time="2025-06-06T11:39:47-07:00" level=info msg="=== END DIFFERENTIAL ANALYSIS ==="
time="2025-06-06T11:39:47-07:00" level=info msg="Starting check table pair: sbtest.sbtest4 => sbtest.sbtest4 ."
time="2025-06-06T11:39:47-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest4 => sbtest.sbtest4 , tableCheckDuration=256.227545ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:47-07:00" level=info msg="End check table pair: sbtest.sbtest4 => sbtest.sbtest4 ."
time="2025-06-06T11:39:47-07:00" level=info msg="Starting check table pair: sbtest.sbtest5 => sbtest.sbtest5 ."
time="2025-06-06T11:39:47-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest5 => sbtest.sbtest5 , tableCheckDuration=254.200657ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:47-07:00" level=info msg="End check table pair: sbtest.sbtest5 => sbtest.sbtest5 ."
time="2025-06-06T11:39:47-07:00" level=info msg="Starting check table pair: sbtest.sbtest6 => sbtest.sbtest6 ."
time="2025-06-06T11:39:48-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest6 => sbtest.sbtest6 , tableCheckDuration=263.125136ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:48-07:00" level=info msg="End check table pair: sbtest.sbtest6 => sbtest.sbtest6 ."
time="2025-06-06T11:39:48-07:00" level=info msg="Starting check table pair: sbtest.sbtest7 => sbtest.sbtest7 ."
time="2025-06-06T11:39:48-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest7 => sbtest.sbtest7 , tableCheckDuration=248.430711ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:48-07:00" level=info msg="End check table pair: sbtest.sbtest7 => sbtest.sbtest7 ."
time="2025-06-06T11:39:48-07:00" level=info msg="Starting check table pair: sbtest.sbtest8 => sbtest.sbtest8 ."
time="2025-06-06T11:39:48-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest8 => sbtest.sbtest8 , tableCheckDuration=251.678157ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:48-07:00" level=info msg="End check table pair: sbtest.sbtest8 => sbtest.sbtest8 ."
time="2025-06-06T11:39:48-07:00" level=info msg="Starting check table pair: sbtest.sbtest9 => sbtest.sbtest9 ."
time="2025-06-06T11:39:49-07:00" level=info msg="Info: record CRC32 checksum value is equal of table pair: sbtest.sbtest9 => sbtest.sbtest9 , tableCheckDuration=253.057682ms, tableCheckSpeed= 100000 rows/second."
time="2025-06-06T11:39:49-07:00" level=info msg="End check table pair: sbtest.sbtest9 => sbtest.sbtest9 ."
time="2025-06-06T11:39:49-07:00" level=error msg="Table records check result 23 equal, 1 not equal."
time="2025-06-06T11:39:49-07:00" level=info msg="Finished go-data-checksum. TotalDuration=9.566251379s"go-data-checksum is a high-performance MySQL database/table data verification tool that provides comprehensive data integrity checking capabilities between MySQL instances. The tool operates by calculating and comparing CRC32 values of data being verified, ensuring accurate and efficient data comparison.
- Primary Key-based Full Data Verification: Complete table comparison using primary keys for chunking and ordering
- Time Field-based Incremental Verification: Incremental data checking based on specified timestamp columns for efficient delta comparisons
- Full Field Verification: Compare all columns in the table for complete data integrity
- Selective Field Verification: Compare only specified columns for targeted verification
- Multi-Table Parallel Verification: Supports parallel verification of multiple tables across different MySQL instances for improved performance
- Superset Data Scenarios: Handles scenarios where target table data is a superset of source table data, allowing for flexible replication validation
- CRC32 Checksum Algorithm: Uses CRC32 checksums to calculate and compare data integrity values, providing fast and reliable data comparison
- Chunk-based Processing: Processes data in configurable chunks to handle large tables efficiently
- Automatic Primary Key Detection: Intelligently selects the best unique key for data chunking and comparison
- Retry Mechanisms: Built-in retry logic for handling transient network or database issues
- Parallel Processing: Multi-threaded execution for concurrent table comparisons
- Memory Efficient: Processes data in chunks to minimize memory usage
- Connection Pooling: Efficient database connection management
- Optimized Query Execution: Smart query building for maximum performance
This tool is designed for database administrators, DevOps engineers, and data engineers who need to ensure data consistency across MySQL environments, validate replication accuracy, and maintain data integrity in distributed database systems.