MongoDB to MySQL sync software for live CDC replication, and the upsert that can update the wrong row
8 min read Buying guides The Adapters team
Field mapping auto-plugged · tap a port to rewire
Plug a source port into
Transform on this cable
JSON in
JSON out
5 sample records ready
MongoDB to MySQL sync software reads the MongoDB change stream and applies every insert, update and delete to MySQL. Estuary and Debezium do it in seconds, AWS DMS until a cutover, and scheduled tools every few minutes. The choice that matters most is on the MySQL side: upserts there match on any unique index, not only the key, so a synced table with a second unique column can update the wrong row.
Key takeaways
- You need a replica set. Change streams do not exist on a standalone mongod, so no CDC tool works there.
- Keep one unique index. MySQL's ON DUPLICATE KEY UPDATE matches any unique index, and MySQL itself advises against it on tables with several.
- Check the server before the vendor. Two popular sinks need local_infile, which MySQL 8 ships disabled.
- Seconds cost more than minutes. Most MySQL copies feed admin panels and reports that are fine five minutes behind.
How do I sync MongoDB to MySQL in real time?
Point a change data capture tool at the MongoDB change stream and let it apply each event to MySQL as
an upsert or a delete. The change stream is an ordered feed built on the oplog, so it exists only on a
replica set or a sharded cluster. A standalone mongod has no change stream at all, and
converting it to a single-node replica set is the first task on the plan, before any vendor demo. After
that the tool reads the feed, keeps a resume token, and writes rows. What separates the products is
what they need from MySQL, how they handle deletes, and how quickly they apply changes.
Sync is the second half of a migration project. If you are still choosing how to do the first load and how each BSON type should land, start with our MongoDB to MySQL migration tools comparison, which covers the defaults on both sides and the date precision problem in detail.
What is the best MongoDB to MySQL sync software?
The best one is the one whose requirements your MySQL server can meet and whose latency matches the consumer. An admin panel or a partner portal reading a MySQL copy is usually fine a few minutes behind. A checkout service reading stock levels is not. Here is how the options compare on the points that decide a real project.
| Option | How it syncs | Latency | Needs on your side | Deletes | Bills by |
|---|---|---|---|---|---|
| Estuary | Change stream capture, continuous materialization | Seconds | local_infile on | Yes | Data moved plus connector instances |
| Debezium plus JDBC sink | Change stream to Kafka, upsert into MySQL | Seconds | Kafka Connect to run | Only with a key mode set | Your infrastructure |
| AWS DMS | Full load plus ongoing replication | Seconds to minutes | Replication instance | Yes | Instance hours |
| Airbyte | Scheduled incremental syncs | Minutes | local_infile on, lowercase names | Depends on sync mode | Free self-hosted, credits on cloud |
| Fivetran | Managed connector, Beta MySQL destination | Minutes | None extra | Soft deletes | Monthly active rows |
| Adapters | Declared mapping, incremental sync | Minutes | A MySQL user with write rights | Yes | Flat, from $49 a month |
Two rows need context. Fivetran does support MySQL as a destination, but lists it as Beta, and its own documentation says MySQL "is not appropriate as a data warehouse". That is a fair warning about analytics, not about operational copies, but it tells you where the vendor puts its effort. And the Debezium row looks free because the software is. Running it means Kafka, Kafka Connect, a schema registry in most setups, and the hosts they sit on. If your team already has a way to provision and patch the servers that stack runs on, the cost is small. If not, that is the line to price honestly.
Why can a MongoDB to MySQL upsert update the wrong row?
Because MySQL's upsert matches on any unique index, not only the primary key. Sync tools write
MongoDB changes with INSERT ... ON DUPLICATE KEY UPDATE, and MySQL's manual explains
that when a table has two unique indexes, the statement behaves like an update matching either one,
with LIMIT 1. If several rows match, "only one row is updated", and the manual's advice
is to "avoid using an ON DUPLICATE KEY UPDATE clause on tables with multiple unique indexes".
Debezium's JDBC sink documents the same thing for MySQL: the update triggers on "any unique index".
Here is how that bites in practice. Your MongoDB users collection has a unique index on
email, so somebody adds the same unique index in MySQL to be safe. A customer changes
email, and a week later a new customer signs up with the old address. In MongoDB those are two
documents with two _id values. In MySQL, if the new customer's insert is applied before
the old customer's email change, as can happen after a replay, the upsert finds the old row through
the email index and overwrites it. The row count is right. One customer has vanished and another has
the wrong history.
The fix costs nothing: give each synced table exactly one unique index, the MongoDB
_id, and enforce the other business rules in the application or in a report. If you need
a unique email for lookups, a plain index gives you the speed without the matching behavior.
How do I keep deletes in sync from MongoDB to MySQL?
Make sure the tool applies delete events, then test it with a real delete before you trust it. This is
not a default everywhere. The Debezium JDBC sink needs delete.enabled=true and a
primary.key.mode other than none, because it deletes by the primary key
mapping. Its insert.mode also defaults to plain insert, so replayed events
either fail on the key or create duplicates until you set upsert. Managed tools vary
between hard deletes and a flag column. Either is fine if you know which one you have, and every
report that counts rows needs to know too.
Does MongoDB to MySQL sync need anything changed on the MySQL server?
For two well-known tools, yes. Airbyte's MySQL destination asks you to run
SET GLOBAL local_infile = true because it loads with LOAD DATA LOCAL INFILE,
and Estuary's MySQL materialization states that the local_infile global variable must be
enabled. MySQL ships that setting disabled, and its manual explains why: a client that allows local
loads can be asked by a malicious server to send files it never meant to send. On a managed production
database, turning it on is a security review, not a checkbox. Ask the question in week one, because
the answer can choose the tool for you.
Airbyte also lowercases every table and column name, and needs MySQL 8.0 or above for typed output.
If your MongoDB documents use both customerId and customerID, which happens
more often than anyone admits, the lowercase names collide.
What should I check before trusting a MongoDB to MySQL sync?
Six things, each tied to a documented behavior rather than a guess. None of them shows up as an error.
| Risk | Documented cause | What to check |
|---|---|---|
| Upsert matches the wrong row | ON DUPLICATE KEY UPDATE fires on any unique index, not only the primary key | Only one unique index per synced table: the MongoDB _id |
| Replays insert duplicates or fail | Debezium JDBC sink defaults to insert.mode=insert | Set insert.mode=upsert before the first event |
| Deletes never arrive | Debezium deletes need delete.enabled and a primary.key.mode other than none | Delete a test document and look for the row |
| Milliseconds rounded away | DATETIME defaults to precision 0 and MySQL rounds without a warning | DATETIME(3) on every date column |
| Sink blocked on day one | Airbyte and Estuary load with LOAD DATA LOCAL, and MySQL ships local_infile off | Security sign-off before tool choice |
| Gap after an outage | The resume token points into the oplog, which is a fixed-size window | Alert on sync lag, and reconcile counts after any long stop |
How much does MongoDB to MySQL sync software cost?
The license is usually the smallest number. Debezium is free and bills you in infrastructure and engineering time. DMS bills replication instance hours for as long as the task runs. Managed pipelines meter something that grows with your data: Fivetran bills monthly active rows plus a $5 base charge per connection, Estuary bills data moved plus connector instances, and Airbyte Cloud bills credits. A metered sync is cheap on a quiet collection and expensive on one where documents are rewritten often, because every rewrite is a change event. For a fuller breakdown of how these models behave when a project runs long, see what a data migration really costs.
Is MySQL the right target, or should MongoDB sync to a warehouse?
It depends on who reads the copy. If an application reads it, such as an admin panel, a PHP service or a partner portal that already speaks MySQL, then MySQL is the right target and the sync should favor correctness of each row. If analysts read it to build dashboards over years of history, a columnar warehouse fits better, and the comparison you want is MongoDB to Snowflake migration tools. Many teams need both, and that is fine: one sync per consumer beats one target serving two jobs badly.
Can I sync MongoDB to MySQL without running Kafka?
Yes. Declare the mapping once and let a managed sync apply the changes on a schedule. With Adapters you
name the document paths that become columns, set dates to DATETIME(3) and money to
DECIMAL, send arrays to child tables keyed on the parent _id, and let every
run upsert on that key alone, deletes included, with a per-record log when something does not fit.
The demo at the top of this page maps a real MongoDB order onto MySQL columns. Plans are flat, from
$49 a month and not metered by rows. For the change capture landscape across other sources, see
change data capture tools, and if PostgreSQL is still an option,
compare MongoDB to PostgreSQL migration tools.
MongoDB into MySQL, keyed on _id and nothing else
Declare the paths that matter, set date precision and decimal scale once, and keep MySQL current with deletes and per-record logs. Flat $49 a month, not metered by rows.
The live demo needs no card, and Starter is $49 a month.