Skip to main content

MySQL to Databricks: CDC via Apache Iceberg

MySQL to Databricks usually means Lakeflow Connect, a Debezium and Kafka pipeline, or nightly dumps picked up by Auto Loader. This guide takes a different route. OLake Go reads MySQL changes from the binary log and commits them as Apache Iceberg tables into Unity Catalog.

The tables land as Unity Catalog managed Iceberg tables, with the files in the catalog's managed storage. Nothing else is in the path: no Kafka, no Debezium, no Databricks ingestion pipeline. DuckDB and Spark read the same tables through the same catalog.

Key takeaways

  • OLake Go replicates MySQL into Databricks as Iceberg tables: a full historical snapshot first, then change data capture (CDC) from the binlog, with no Kafka or Debezium in between.
  • The tables are Unity Catalog managed Iceberg tables. You query them from the Databricks SQL editor or a notebook right away, with no external table and no refresh.
  • The same tables, written once by OLake Go, are also queryable from DuckDB and Spark through Unity Catalog. In the run on this page all three engines returned identical rows.

The short answer. To replicate MySQL to Databricks with change data capture (CDC), run OLake Go with MySQL's binary log as the source and Unity Catalog as the Iceberg destination, with Databricks as a target query engine. The first sync copies each table in full (the historical snapshot), and every later sync replays inserts, updates and deletes from the binlog. Databricks, DuckDB and Spark then query the same Iceberg tables, with no second copy.

How MySQL CDC reaches Databricks here​

Two things happen in order, and OLake Go does both on its own. The first sync records the current binlog position, then reads each table in parallel chunks and writes Parquet data files: that is the historical load. Every later sync opens the binlog at the saved position and replays the inserts, updates and deletes since.

Nothing written during the snapshot is lost, and there is no separate backfill job. An update or delete retires the old row with a deletion vector, a small bitmap per data file that marks the replaced positions.

Each sync ends with one commit to Unity Catalog. Databricks sees the new snapshot on its next query, and DuckDB and Spark see it on their next catalog load.

One thing to keep an eye on: the binlog is MySQL's replay window. MySQL deletes binlog files after binlog_expire_logs_seconds, and a job paused for longer than that cannot resume without a fresh snapshot. Keep retention longer than your longest planned pause, and alert on SHOW BINARY LOGS size if disk is tight.

What you need​

  • MySQL 5.7 or later with the binlog on in ROW format and a user that can read it. The tables on this page ran on MySQL 8.4. Hosted MySQL on Amazon Relational Database Service (RDS), Aurora, Azure or Cloud SQL works the same way; step 1 links each guide.
  • A Databricks workspace with Unity Catalog, a catalog with managed storage, and an admin who can turn on external data access and grant EXTERNAL USE SCHEMA. The run on this page used a workspace on Google Cloud.
  • OLake UI from the Docker Compose install. The run on this page used OLake Go v1.1.0.
  • Docker, and about 30 minutes.

Step 1: Prepare MySQL for CDC​

Most readers point OLake Go at tables they already have. If you want to follow along with the exact data from this page, create these two tables first:

The two sample tables used on this page
Sample schema
CREATE TABLE customers (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100), email VARCHAR(200), city VARCHAR(100), tier VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP);

CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT, product VARCHAR(100), amount DECIMAL(10,2), status VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (customer_id) REFERENCES customers(id));

-- Then insert whatever you like. This page started with 8 customers and 40 orders.

OLake Go reads MySQL changes from the binary log (binlog), the same stream MySQL replicas use. Four things must be in place on the server before the first sync.

SettingValueWhy it matters
log_binON with a server_idWithout a binlog MySQL records no row changes, so there is nothing to replicate. Changing it needs a restart.
binlog_formatROWStatement-based logging does not carry the row images OLake Go needs.
binlog_row_metadataFULLAdds column names and types to every event, so OLake Go can map changes to columns after a schema change.
Replication userSELECT, REPLICATION SLAVE, REPLICATION CLIENTThe snapshot reads the tables; the change stream reads the binlog.
On the MySQL server
-- 1. Binary logging: put these in my.cnf (or the parameter group on managed MySQL) and restart
-- server-id = 1
-- log_bin = mysql-bin
-- binlog_format = ROW
-- binlog_row_metadata = FULL
-- binlog_expire_logs_seconds = 604800 -- keep 7 days, so a paused job can catch up
-- gtid_mode = ON, enforce_gtid_consistency = ON -- optional; gives microsecond _cdc_timestamp

-- 2. A user for OLake Go
CREATE USER IF NOT EXISTS 'olake'@'%' IDENTIFIED BY '<password>';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'olake'@'%';
FLUSH PRIVILEGES;

-- 3. Table statistics: the chunked snapshot plans its chunks from them
ANALYZE TABLE customers, orders;

Check the result before moving on:

sql
SHOW VARIABLES WHERE Variable_name IN ('log_bin', 'binlog_format', 'binlog_row_metadata', 'server_id');
SHOW GRANTS FOR 'olake'@'%';

Managed MySQL sets these through a parameter group or server setting rather than my.cnf. Each flavour has its own guide:

Where MySQL runsGuide
Amazon RDSRDS MySQL CDC setup
Amazon AuroraAurora MySQL CDC setup
Azure Database for MySQLAzure MySQL CDC setup
Google Cloud SQLCloud SQL MySQL CDC setup
Your own server or VMGeneric MySQL CDC setup
Docker, for a local testLocal MySQL with Docker Compose
Binlog retention is your replay window

MySQL deletes binlog files after binlog_expire_logs_seconds. If a job is paused longer than that, the next sync cannot find its saved position and needs a fresh full snapshot. Keep retention longer than your longest planned pause.

Step 2: Set up Unity Catalog in Databricks​

Databricks reads Iceberg tables through Unity Catalog, the catalog built into every Databricks workspace. OLake Go writes straight into it, so the tables become Unity Catalog managed Iceberg tables. They show up in Catalog Explorer and the SQL editor the moment a sync commits.

The files sit in the catalog's managed storage, which is backed by an external location in your own cloud account. DuckDB and Spark reach the same tables through the same Unity Catalog endpoint.

CatalogOLake Go writesDatabricks readsVerified on this page
Unity CatalogYesYes: native: Unity Catalog managed Iceberg tablesYes, end to end

Unity Catalog is the catalog built into every Databricks workspace. OLake Go writes to it through Unity Catalog's Iceberg REST (representational state transfer) endpoint at https://<workspace-url>/api/2.1/unity-catalog/iceberg-rest, and the tables it creates are Unity Catalog managed Iceberg tables. Databricks allows writes through this endpoint for managed Iceberg tables only (Databricks: access tables from Iceberg clients, checked 2026-10-30).

Four things must be in place in the workspace, once:

SettingWhereWhy it matters
External data access on the metastoreCatalog, the gear icon, Metastore, then External data accessWithout it the Iceberg REST endpoint refuses every client that is not Databricks itself.
A catalog with managed storageCatalog, Create catalog, with a storage location backed by an external locationOLake Go creates one schema per job inside it. The catalog name is the warehouse value every client uses.
EXTERNAL USE SCHEMA for the writing principalOn the catalog, so it covers the schemas OLake Go createsUnity Catalog checks this privilege, on top of the usual ones, before it serves a table to an outside engine.
A personal access token or a service principalSettings, Developer, Access tokensOLake Go sends it as a bearer token on every catalog call.
Databricks SQL editor, once per catalog (replace the principal)
-- The catalog OLake Go writes into. Its managed storage holds the Iceberg files.
CREATE CATALOG IF NOT EXISTS lakehouse MANAGED LOCATION '<external location URL>/lakehouse';

-- What the writing principal needs: browse and create schemas, then read and write tables in them
GRANT USE CATALOG, CREATE SCHEMA ON CATALOG lakehouse TO `olake@example.com`;
GRANT USE SCHEMA, CREATE TABLE, SELECT, MODIFY ON CATALOG lakehouse TO `olake@example.com`;
GRANT EXTERNAL USE SCHEMA ON CATALOG lakehouse TO `olake@example.com`;

Granting on the catalog makes the privileges inherit to every schema OLake Go creates later. EXTERNAL USE SCHEMA is not part of ALL PRIVILEGES, and only the catalog owner can grant it (Databricks: Unity Catalog privileges, checked 2026-10-30). A service principal with OAuth machine-to-machine credentials is the better choice in production. The other authentication methods are on the Unity Catalog destination page.

Databricks reads deletion vectors only

Unity Catalog accepts updates to a managed Iceberg table only as copy-on-write files or as Iceberg v3 deletion vectors. It rejects equality delete files with Can not create a Path from a null string [ErrorCode: 2015]. Set Databricks as a target query engine on the job, and OLake Go writes deletion vectors from the first sync.

Step 3: Connect MySQL to OLake Go​

  1. In the OLake UI, open Sources and click Create Source. Pick MySQL as the connector, give the source a name and keep the OLake Go version on the latest release.
  2. Under Change data capture (CDC), leave Enable CDC on. The default Initial Wait Time is fine; raise it only if the binlog is large and the next sync times out while finding its saved position.
  3. Under Connection details, fill in the host, port, database name, username and password. Keep TLS off for a local test; on managed MySQL use the provider's certificate.
  4. Click Set up source. OLake Go tests the connection, then checks the binlog prerequisites (binlog_access, log_bin, binlog_format, binlog_row_metadata) and shows the current and recommended value for anything that fails.

Here is the source form from the run on this page. MySQL ran in another container, so the host is host.docker.internal; the user is the replication user from step 1.

OLake UI Edit source form for MySQL on OLake Go v1.1.0 with Enable CDC on, initial wait time 10, host host.docker.internal, database shop, user olake and SSL disabled
The MySQL source in OLake UI: Enable CDC on, with the replication user from step 1.

Set up source runs the connection test and the binlog checks (binlog_access, log_bin, binlog_format, binlog_row_metadata) before anything is saved. When one fails, the dialog names the setting and its recommended value.

The full field reference, including SSL and SSH tunnels, is on the MySQL connector page.

Step 4: Configure the Iceberg destination​

  1. Open Destinations and click Create Destination.
  2. Pick Apache Iceberg as the connector and Unity as the catalog type.
  3. Choose Personal Access Token as the authentication type and fill in the fields below. The catalog name from the setup goes into the catalog field.
  4. Click Create. OLake Go calls the Unity Catalog endpoint with the token and saves the destination.
OLake UI destination form for an Apache Iceberg destination on the Unity catalog type with the personal access token fields

The run on this page wrote into a catalog called datazip, with a personal access token that expires after seven days. The snippets below use lakehouse; replace it with your catalog name.

Unity Configuration Parameters

ParameterSample ValueDescription
REST Catalog URL
required
https://1234567890123456.1.gcp.databricks.com/Databricks workspace URL with Unity Catalog REST API endpoint. Use your actual workspace URL.
Authentication Type
required
Personal Access TokenAuthentication method OLake Go uses for Unity Catalog REST API requests. Set to match your workspace's authentication configuration.
Unity Catalog Name
required
workspaceName of the catalog in Unity.
Token
required
dapi123456789...Databricks Personal Access Token
Catalog Nameolake_icebergName of the Iceberg catalog OLake Go registers tables under. Defaults to olake_iceberg if left empty.
Enable Arrow WritesfalseWrites data and delete files using Apache Arrow based writer and registers them in Iceberg.
Catalog Name Supported for v0.3.5 and above
For the catalog name, OLake Go only supports lowercase letters and underscores. Spaces and special characters are not supported.

Step 5: Create the job and run the first sync​

  1. Open Jobs and click Create Job. Give it a name and a schedule. Every Day at a fixed hour, or a cron expression, both work; the schedule is how often the change stream is drained, not how often a full copy runs.
  2. Select the source and the destination you created.
  3. Under Advanced Settings, set Target Query Engines to Databricks, plus DuckDB and Apache Spark if they will read the tables too. OLake Go then resolves an Iceberg delete format every one of those engines can read, so you never pick one by hand.
  4. Click Next. OLake Go discovers the tables and opens the Streams page.
  5. Tick the tables to replicate. For each one, set the sync mode to Full Refresh + CDC and the ingestion mode to Upsert. Leave the Upsert Type as OLake Go resolved it (Deletion Vector in the run on this page).
  6. Click Create Job, then open the job's menu and choose Sync Now for the first run.

Three target query engines were set on this job, and Databricks narrows the delete formats to one: deletion vectors on Iceberg v3 tables. OLake Go sets that format on every stream, so there is nothing to pick.

OLake UI Streams Selection page with the shop database, customers and orders selected, sync mode Full Refresh + CDC, ingestion mode Upsert and upsert type Deletion Vector on Iceberg format version V3
Each stream: Full Refresh + CDC, Upsert, and Deletion Vector on Iceberg v3, the format OLake Go resolved for Databricks.
OLake UI Job Logs and History for mysql_to_databricks showing two completed syncs, the first in 25 seconds and the second in 22 seconds
Job history: the 25 s historical snapshot, then the 22 s change-only sync.

On this page the first run copied 8 customers and 40 orders (48 records, 2.10 KB) and finished in 25 seconds, container start included. OLake Go names the Unity Catalog schema after the job and the source database, so mysql_to_databricks on database shop produced mysql_to_databricks_shop. The Iceberg DB field at the top of the Streams page changes it before the first sync.

Step 6: Query the tables in Databricks​

There is no registration step. The schema and tables OLake Go committed through Unity Catalog are managed Iceberg tables in the catalog you configured, and the Databricks SQL editor or a notebook reads them by their three-part name.

Databricks SQL editor
-- What OLake Go created (schema = <job name>_<source database>[_<source schema>])
SHOW TABLES IN lakehouse.mysql_to_databricks_shop;

SELECT id, name, city, tier
FROM lakehouse.mysql_to_databricks_shop.customers
ORDER BY id;

SELECT status, count(*) AS n, round(sum(amount), 2) AS total
FROM lakehouse.mysql_to_databricks_shop.orders
GROUP BY status ORDER BY status;

Any SQL warehouse or cluster on a recent Databricks Runtime reads them; a 2X-Small serverless warehouse is enough for checks. DESCRIBE EXTENDED on a table shows the Iceberg provider and the deletion vectors in its properties.

OLake Go adds four columns to every table: _olake_id (a hash of the primary key), _op_type, _olake_timestamp and _cdc_timestamp. _op_type is r for a row from the first snapshot, c for an insert, u for an update and d for a delete. Filter them out of dashboards, or use them to see when a row last changed.

Databricks SQL editor running SELECT on datazip.mysql_to_databricks_shop.customers on the 2X-Small SQL warehouse, returning 10 rows with id, name, city and tier
The MySQL customers table in the Databricks SQL editor after both syncs: 10 rows, read through Unity Catalog.

MySQL tables carry two more OLake Go columns, _cdc_binlog_file_name and _cdc_binlog_file_pos, which record where in the binlog each changed row came from.

Step 7: Change rows in MySQL and sync again​

To see the change path work, the run on this page made 12 changes in MySQL after the first sync:

Changes made in MySQL between the two syncs
INSERT INTO customers (name, email, city, tier) VALUES
('Maya Patel', 'maya@example.com', 'Pune', 'silver'),
('Lucas Silva', 'lucas@example.com', 'São Paulo', 'bronze');
INSERT INTO orders (customer_id, product, amount, status) VALUES
(9, 'Laptop', 1299.00, 'placed'), (9, 'Mouse', 25.50, 'placed'),
(10, 'Monitor', 349.99, 'placed'), (3, 'Keyboard', 89.00, 'placed'), (7, 'Webcam', 79.90, 'placed');
UPDATE customers SET tier = 'gold', updated_at = NOW() WHERE name = 'Liam Carter';
UPDATE customers SET city = 'Abu Dhabi', updated_at = NOW() WHERE name = 'Zara Ali';
UPDATE orders SET status = 'delivered', updated_at = NOW() WHERE id IN (1, 4, 7);

The second sync read 12 records (563 bytes) from the binlog and finished in 22 seconds end to end. For customers that meant one new data file with 4 rows: 2 inserts plus the new versions of 2 updated rows. One deletion vector retires the 2 old versions.

OLake Go's commit report in the sync log shows it directly: addedDataFiles=1, addedRecords=4, addedDVs=1 and addedPositionalDeletes=2. The orders table has the same shape with 8 rows and 3 deleted positions, and the equality delete count stays at 0.

OLake UI sync log filtered to the Iceberg commit reports for customers and orders after the change sync, showing addedDataFiles 1, addedDVs 1, addedRecords 4 and 8, and addedPositionalDeletes 2 and 3
The change-only sync in the OLake UI log: one data file and one deletion vector per table, zero equality deletes.

Databricks shows the result on the next query, with no refresh. Liam Carter is gold, Zara Ali moved to Abu Dhabi, and the two new customers are there. The _op_type column shows how each row arrived: r for the 6 untouched snapshot rows, c for the 2 inserts and u for the 2 updates.

Schema changes. When a column is added in MySQL, OLake Go adds it to the Iceberg table on the next sync and commits the new schema to Unity Catalog. Widening changes such as int to bigint are applied, while narrowing ones fail the sync rather than corrupt the table. binlog_row_metadata = FULL lets OLake Go see the new column names in the binlog; full rules are on the schema evolution page.

Query the same tables from DuckDB and Spark​

Nothing is exported, copied or refreshed. Both engines read the files OLake Go wrote, through the same Unity Catalog endpoint, with storage credentials vended by Databricks.

DuckDB attaches the Unity Catalog catalog as an Iceberg REST catalog with the same token. With vended_credentials, Unity Catalog hands DuckDB short-lived storage credentials per table, so no cloud keys are configured on the DuckDB side.

DuckDB 1.5.5
INSTALL iceberg; LOAD iceberg;
INSTALL httpfs; LOAD httpfs;
CREATE SECRET unity (TYPE iceberg, TOKEN '<personal access token>');
ATTACH 'lakehouse' AS uc (
TYPE iceberg,
ENDPOINT 'https://<workspace-url>/api/2.1/unity-catalog/iceberg-rest',
SECRET unity,
ACCESS_DELEGATION_MODE 'vended_credentials'
);
SELECT id, name, city, tier
FROM uc.mysql_to_databricks_shop.customers
ORDER BY id;

DuckDB reads Iceberg v3 deletion vectors from duckdb-iceberg 1.5.3, so the rows match Databricks after every sync. The principal behind the token needs EXTERNAL USE SCHEMA, the same as OLake Go.

DuckDB 1.5.5 returned the same 10 customers and the same orders roll-up as Databricks in the run on this page.

An orders roll-up matched MySQL to the cent in all three engines. It gave 16 delivered orders for 8659.68, 18 placed for 10522.26, 11 shipped for 6397.25.

Data types: MySQL to Iceberg to Databricks​

OLake Go maps MySQL types to Iceberg types, and Databricks maps Iceberg types to its own. Most columns pass through unchanged; decimals and date-times are the ones to know.

MySQLIceberg (written by OLake Go)Databricks showsNotes
int, mediumint, smallint, tinyint (signed or unsigned)intINTThe id and customer_id columns on this page arrived as INT.
bigint, bigint unsignedlongBIGINT
decimal(p,s)doubleDOUBLEDESCRIBE showed orders.amount as DOUBLE in this run, and its sums matched MySQL to the cent. Round at query time when you need exact decimal arithmetic.
floatfloatFLOAT
double, realdoubleDOUBLE
datetime, timestamptimestamptz (UTC)TIMESTAMPdatetime has no zone in MySQL; it is read as UTC.
char, varchar, text family, enum, json, time, bit(1)stringSTRINGtime arrives as text.

MySQL types in the table come from the MySQL connector reference; the Databricks side comes from DESCRIBE on the tables of this run.

Cost and maintenance​

  • Storage is the cloud bucket behind the catalog's managed location. You pay your cloud's storage rates for the Parquet data files and deletion vectors.
  • Compute. OLake Go runs wherever you run Docker and does not use a Databricks warehouse or cluster to write. Databricks compute is spent only when you query; the checks on this page ran on a 2X-Small serverless SQL warehouse.
  • Maintenance. Unity Catalog runs snapshot expiration and file compaction on managed Iceberg tables when predictive optimization is on (Databricks: Iceberg tables (checked Oct 2026)). Check that it is on for the catalog OLake Go writes to. Spark's rewrite_data_files also works through the same Unity Catalog endpoint.
  • Token rotation. A personal access token expires on the date set when it was created, and an expired token fails the next sync at the first catalog call. Use a service principal with OAuth machine-to-machine credentials for production jobs.
  • Binlog disk. Seven days of binlog on a busy server can be tens of gigabytes. Size the volume for it, or shorten retention once the job runs reliably every hour.

Troubleshooting​

1. The first sync stops with a statistics error​

failed to get or split chunks: stats not populated for table[shop.orders]. Please run ANALYZE TABLE to update table statistics

OLake Go plans the parallel snapshot from information_schema row estimates, and a freshly created or freshly loaded table has none. Run ANALYZE TABLE <table>; for each table in the job, then sync again.

2. The source test fails on the binlog​

binlog_format: current STATEMENT, recommended ROW

The test dialog names the setting. binlog_format and binlog_row_metadata can be changed at runtime with SET GLOBAL; put them in my.cnf (or the parameter group) too, or they revert on restart. log_bin itself needs a restart.

3. The next sync cannot find its binlog position​

idle timeout reached, exiting binlog syncer

Two causes. If binlog_expire_logs_seconds already removed the saved binlog file, run a fresh full snapshot and lengthen retention. If the file exists but the server is slow to seek to the position, raise Initial Wait Time on the source; 10 seconds was enough for the two-table database on this page.

4. Stream discovery fails with a bad connection​

failed to ping database: driver: bad connection

The connection dropped while OLake Go was listing the streams, which happened once in the run on this page while several jobs discovered at the same moment. Open the job's streams again; the retry discovered both tables.

5. The destination test fails with a permission error​

Three settings cover nearly every failure: External data access is off on the metastore, the principal lacks EXTERNAL USE SCHEMA on the catalog, or the catalog name in the destination is wrong. ALL PRIVILEGES does not include EXTERNAL USE SCHEMA, so a catalog owner still has to grant it explicitly. Run the grants from step 2 again for the principal that owns the token.

6. Databricks rejects a commit with "Can not create a Path from a null string"​

Can not create a Path from a null string [ErrorCode: 2015]

Unity Catalog returns this when a client commits equality delete files to a managed Iceberg table (Databricks: access tables from Iceberg clients (checked Oct 2026)). Set Databricks as a target query engine on the job, so OLake Go writes deletion vectors. Start any table that was written with another delete format again from a full refresh.

How this compares with other ways to move MySQL data to Databricks
MethodFreshnessUpdates and deletesWhere the data landsWhat you run
Lakeflow Connect MySQL connector (checked Oct 2026)CDC, run by DatabricksYes, through CDCStreaming tables governed by Unity CatalogNothing to host; runs on Databricks serverless compute
Debezium, Kafka and Structured Streaming from Kafka (checked Oct 2026)SecondsYes, with your own merge logicKafka topics, then tables written by a Databricks streaming jobKafka, Kafka Connect, Debezium and a running Databricks job
mysqldump or SELECT ... INTO OUTFILE to a bucket, then COPY INTO or Auto Loader (checked Oct 2026)Hours to dailyNo; full reloads or your own mergeTables written by a Databricks jobAn export script, a bucket and a Databricks job
OLake Go to Iceberg on Unity Catalog (this guide)CDC on a schedule down to one minuteYes, as deletion vectorsApache Iceberg tables that Databricks, DuckDB and Spark all readOne Docker Compose stack, open source; your storage plus your own compute

Lakeflow Connect is the simpler choice when Databricks is the only reader and you want Databricks to run the pipeline. Writing Iceberg once avoids a second copy as soon as another engine needs the same rows.

When not to use this​

  • Tables have no primary key. Inserts replicate; updates and deletes cannot be matched to a row and are not applied. Add a primary key or a unique index first.
  • Every query must run on Databricks-native tables. OLake Go writes managed Iceberg tables. If a pipeline needs native Delta Lake features on these rows, load them into a Delta table downstream.

FAQs​

How do I replicate MySQL to Databricks without a managed connector?

Run OLake Go (open source, Apache 2.0) against a MySQL server with row-based binary logging turned on, and point it at Unity Catalog's Iceberg REST endpoint. The first sync copies the tables in full, every later sync replays only the changes from the binlog, and the tables appear in Databricks as Unity Catalog managed Iceberg tables. No Kafka, Debezium or Databricks ingestion pipeline sits in the path.

Does OLake Go copy the existing MySQL data before it starts reading the binlog?

Yes, automatically. A job in Full Refresh + CDC mode records the binlog position, copies every selected table in parallel chunks, then switches to the binlog from that position. Nothing written during the copy is lost, and there is no separate backfill job.

Which Iceberg delete format does OLake Go write for Databricks?

Deletion vectors, on Iceberg v3 tables. Under Unity Catalog, Databricks reads deletion vectors and rejects commits that carry equality deletes. Setting Databricks as a target query engine on the job makes OLake Go pick deletion vectors from the first sync, so there is no format to choose.

How is this different from the Lakeflow Connect MySQL connector?

Lakeflow Connect lists MySQL among its managed CDC database connectors; it runs on Databricks serverless compute and writes streaming tables governed by Unity Catalog (checked Oct 2026). OLake Go runs in your own Docker and writes Iceberg tables, so DuckDB and Spark read the same rows Databricks does. Pick Lakeflow Connect when Databricks is the only reader.

Which MySQL data types change on the way to Databricks?

decimal columns arrive in Databricks as DOUBLE, so round at query time when you need exact decimal arithmetic. datetime and timestamp become TIMESTAMP in UTC. json, enum, time and bit(1) arrive as STRING, and integers and strings map one to one.

How much binlog does MySQL need to keep for OLake Go?

Enough to cover your longest pause. OLake Go saves the binlog position after every sync and resumes from it; if MySQL has already expired that file, the next sync cannot resume and needs a fresh snapshot. Seven days (binlog_expire_logs_seconds = 604800) is a safe default for a job that runs at least daily.

Deeper reading on this site: