Compare SQL dump files and generate delta update scripts
Project description
SQL Dump Diff
A Python tool to compare two SQL dump files and generate a delta SQL script containing INSERT, UPDATE, and DELETE statements to transform the old dump into the new dump.
Features
- 🔍 Schema-aware comparison: Automatically detects PRIMARY KEY constraints to identify records
- 📊 Progress tracking: Visual progress bars for large dump files using
tqdm - 📈 Summary statistics: Displays counts of inserts, updates, and deletes at the end
- 🔄 Multi-line support: Handles multi-line CREATE TABLE and INSERT statements
- 📦 Multi-row INSERT support: Handles default mysqldump multi-value INSERTs and INSERTs without explicit column lists
- 🧵 Parallel per-table diffing: Splits dumps by table and optionally processes tables in parallel
- 🎯 Composite keys: Supports tables with composite primary keys
- 📝 Delta generation: Produces a complete SQL script with:
- INSERT statements for new records
- UPDATE statements for modified records
- DELETE statements for removed records
- ⚡ Efficient processing: Indexes records by primary key for fast lookups
- 🔌 Flexible output: Output to file or stdout for easy piping to other commands
Installation
This project uses uv for dependency management.
Prerequisites
- Python 3.14 or higher
- uv installed
Setup
-
Clone the repository:
git clone <repository-url> cd sqldumpdiff
-
Install dependencies with uv:
uv sync
Usage
Basic Usage
The script accepts two required arguments and an optional third argument:
uv run sqldumpdiff.py <old_dump.sql> <new_dump.sql> [output.sql]
- If
output.sqlis provided: The delta script is written to that file - If
output.sqlis omitted: The delta script is printed to stdout (useful for piping)
Note: Progress messages and summary statistics are always printed to stderr, so they won't interfere with stdout output when piping.
Examples
Write to a file
uv run sqldumpdiff.py database_old.sql database_new.sql delta.sql
This creates delta.sql with the delta script.
Print to stdout
uv run sqldumpdiff.py database_old.sql database_new.sql
The delta SQL is printed to stdout. Progress messages appear on stderr.
Pipe to MySQL
uv run sqldumpdiff.py database_old.sql database_new.sql | mysql mydatabase
Pipe to a file
uv run sqldumpdiff.py database_old.sql database_new.sql > delta.sql
Output Example
The generated delta script will contain:
-- Full Delta Update Script
SET FOREIGN_KEY_CHECKS = 0;
-- NEW RECORD IN users
INSERT INTO `users` (`id`, `name`, `email`) VALUES ('1', 'John Doe', 'john@example.com');
-- email old value: old@example.com
UPDATE `users` SET `email`='new@example.com' WHERE `id`='1';
-- DELETIONS
-- DELETED FROM users: ('2',)
DELETE FROM `users` WHERE `id`='2';
SET FOREIGN_KEY_CHECKS = 1;
Summary Output
After processing, the script displays a summary on stderr:
============================================================
SUMMARY
============================================================
Inserts: 1,234
Updates: 567
Deletes: 89
Total: 1,890
============================================================
How It Works
The tool performs the following steps:
- Schema Parsing: Extracts PRIMARY KEY definitions and column order from CREATE TABLE statements
- Per-table splitting: Streams INSERT statements and writes per-table row files (handles multi-row VALUES)
- Comparison (parallel-capable): For each table, compares new rows vs old by primary key
- Records not found in the old dump → INSERT statements
- Records with changed values → UPDATE statements
- Records matched are tracked to identify deletions
- Deletion Detection: Identifies records present in the old dump but missing from the new dump → DELETE statements
Requirements
- Python 3.14+
tqdmfor progress bars
Limitations
- Tables without PRIMARY KEY constraints are skipped (records cannot be uniquely identified)
- Requires both dump files to be valid SQL with proper encoding (UTF-8)
Performance
Optimization Strategies
The tool uses multiple strategies to handle large SQL dumps efficiently:
-
Streaming approach: Processes data incrementally to handle files larger than available memory
-
Parallel processing:
- INSERT statement parsing can be parallelized during the split phase using process-based parallelism to bypass Python's GIL
- Table comparison runs in parallel with configurable workers
- Both old and new files are split concurrently
Environment Variables
Parallelism is enabled by default when more than one table exists. Fine-tune performance with these environment variables:
Table Comparison
SQLDUMPDIFF_PARALLEL=0- Disable parallel processing entirelySQLDUMPDIFF_WORKERS=<n>- Set worker count for table comparison (default: CPU cores)SQLDUMPDIFF_WORKERS=-1- Use as many workers as there are tables (subject to hard cap)SQLDUMPDIFF_MAX_WORKERS=<n>- Hard cap on workers (default: 4x CPU cores)SQLDUMPDIFF_EXECUTOR=thread- Use threads instead of processes for table comparison (default: process)
INSERT Parsing Optimization
SQLDUMPDIFF_PARALLEL_INSERTS=1- Enable parallel INSERT processing during splitting (recommended for large files)SQLDUMPDIFF_INSERT_WORKERS=<n>- Worker count for INSERT parsing (default: CPU cores)SQLDUMPDIFF_INSERT_EXECUTOR=thread- Use threads instead of processes (default: process)
Performance Tips
For best performance with large dumps:
# Recommended settings for large files
SQLDUMPDIFF_PARALLEL_INSERTS=1 \
SQLDUMPDIFF_EXECUTOR=thread \
SQLDUMPDIFF_WORKERS=-1 \
uv run sqldumpdiff.py old.sql new.sql > delta.sql
Note: Multi-row INSERTs are expanded per row during splitting to avoid redundant parsing during comparison.
Benchmarking
To find the optimal worker settings for your system, use the provided benchmark script:
./benchmark.sh 11.sql 22.sql
The script will test various configurations and save results with timing information. Common findings:
- Process-based parallelism (
INSERT_EXECUTOR=process) performs better than threads for CPU-intensive INSERT parsing - Parallel INSERT processing (
PARALLEL_INSERTS=1) significantly speeds up large files (3-4x improvement) - Worker count: Start with CPU cores, experiment with 2x CPU cores for I/O bound workloads
- Table comparison: Thread-based (
EXECUTOR=thread) often performs better than processes when you have many small tables
Review benchmark_results_*.txt to identify the fastest configuration for your data.
Language Performance Considerations
Python's GIL Limitations
Python's Global Interpreter Lock (GIL) limits true parallel CPU execution for threads. While process-based parallelism (as used in this tool) bypasses the GIL, it has overhead for inter-process communication and memory duplication.
Alternative Languages for Better Performance
If you need significantly faster execution, consider these alternatives:
1. Rust (Recommended - Best Performance)
- Speed: 5-10x faster than Python
- Pros:
- True parallelism without GIL
- Zero-cost abstractions
- Memory safety without garbage collection
- Excellent for CPU-intensive parsing
- Single binary deployment
- Cons: Steeper learning curve, longer development time
- Best for: Maximum performance, production deployments at scale
2. Go (Recommended - Best Balance)
- Speed: 3-5x faster than Python
- Pros:
- Native concurrency with goroutines (cheap, lightweight)
- Simple syntax, easy to learn
- Fast compilation
- Single binary deployment
- Good standard library for parsing
- Cons: Less control than Rust, garbage collection overhead
- Best for: Fast development with good performance gains
3. C++ (Maximum Speed)
- Speed: 10-20x faster than Python (when optimized)
- Pros:
- Absolute maximum performance
- Full control over memory and threading
- Cons: Complex, manual memory management, longer development
- Best for: When every millisecond counts
4. Java/Kotlin (Enterprise Option)
- Speed: 2-4x faster than Python
- Pros:
- Mature ecosystem
- Good JVM threading
- Great tooling
- Cons: JVM startup overhead, heavier runtime
- Best for: Enterprise environments with existing Java infrastructure
Realistic Performance Gains
Current Python implementation with optimizations: ~5:43 for your dataset
Estimated rewrite times:
- Go: ~2-3 minutes (50-60% faster)
- Rust: ~1-2 minutes (70-80% faster)
- C++: ~1 minute (80-90% faster, highly optimized)
Recommendation
For most use cases, the current Python implementation with PARALLEL_INSERTS=1 is sufficient. Consider a rewrite only if:
- You run this tool very frequently (hundreds of times per day)
- You need sub-minute execution for large dumps
- You want to distribute a zero-dependency binary
Quick win: The Go rewrite would be the best ROI (2-3 weeks development for 3-5x speedup).
Output Format
The generated delta script:
- Disables foreign key checks at the start
- Includes comments indicating the type of change (NEW RECORD, UPDATE, DELETION)
- For UPDATEs, includes comments showing the old values
- Re-enables foreign key checks at the end
Building a Native Executable
You can create a standalone executable that doesn't require Python to be installed.
Prerequisites
Install build dependencies:
uv sync --extra dev
Building
Use the provided build script:
./build.sh
Or build manually with PyInstaller:
uv run pyinstaller --onefile --name sqldumpdiff sqldumpdiff.py
The executable will be created in the dist/ directory.
Using the Executable
After building, you can use the executable directly:
# Write to file
./dist/sqldumpdiff <old_dump.sql> <new_dump.sql> output.sql
# Print to stdout
./dist/sqldumpdiff <old_dump.sql> <new_dump.sql>
The executable is platform-specific (macOS, Linux, or Windows) and includes all dependencies, so it can be distributed without requiring Python or any packages to be installed.
Development
Running the Script
# With output file
uv run python sqldumpdiff.py <old_dump.sql> <new_dump.sql> <output.sql>
# Print to stdout
uv run python sqldumpdiff.py <old_dump.sql> <new_dump.sql>
Project Structure
sqldumpdiff/
├── sqldumpdiff.py # Main script
├── pyproject.toml # Project configuration
├── build.sh # Build script for executable
├── sqldumpdiff.spec # PyInstaller spec file
├── uv.lock # Dependency lock file
└── README.md # This file
License
[Add your license here]
Contributing
[Add contribution guidelines here]
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sqldumpdiff-0.1.0.tar.gz.
File metadata
- Download URL: sqldumpdiff-0.1.0.tar.gz
- Upload date:
- Size: 13.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.0.1 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b48cc5f7b3fbd59d894412c6da549c8d6a2ffc29a7dd63a958ddd5232041d01b
|
|
| MD5 |
6c63f70a4a93367e47f84411cbd8b057
|
|
| BLAKE2b-256 |
71508f1758b0f9a79e8b48f2e8ab9a893c9040cb5642bf3379d0e6fb5f95d974
|
File details
Details for the file sqldumpdiff-0.1.0-py3-none-any.whl.
File metadata
- Download URL: sqldumpdiff-0.1.0-py3-none-any.whl
- Upload date:
- Size: 13.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.0.1 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aeff2abe2d9efeaa4c9a27de7a67ab2bff47f01e999277207a4e8904f358c04c
|
|
| MD5 |
0b84078efb37717e3397f3df9bb7ba8c
|
|
| BLAKE2b-256 |
aa00c18220a0fc9e5e223ca33b6c90bccef4c2c26a49ffdecc17521483035901
|