|
| 1 | +# MySQL CDC (Binlog Replication) |
| 2 | + |
| 3 | +Works with `v2.2.0+` |
| 4 | + |
| 5 | +This recipe demonstrates how to stream real-time changes from a MySQL table into Spice using native Change Data Capture (CDC) over the MySQL [binary log](https://dev.mysql.com/doc/refman/8.0/en/binary-log.html). Inserts, updates, and deletes propagate automatically to the Spice accelerator — no Debezium or Kafka required. |
| 6 | + |
| 7 | +Spice reads the binlog directly and applies row-level changes by primary key. The current binlog file and position are checkpointed in a client-side sidecar table so streaming resumes from where it left off after a restart. |
| 8 | + |
| 9 | +## Prerequisites |
| 10 | + |
| 11 | +- [Docker](https://docs.docker.com/get-docker/) is installed |
| 12 | +- Spice is installed (see the [Getting Started](https://docs.spiceai.org/getting-started) documentation) |
| 13 | + |
| 14 | +> **Note:** MySQL CDC requires the server to be running with row-based binary logging enabled (`log_bin=ON`, `binlog_format=ROW`, `binlog_row_image=FULL`). MySQL 8.0+ enables `log_bin` and `binlog_format=ROW` by default; this recipe passes the flags explicitly so the requirement is clear. GTID-based positioning is not required. |
| 15 | +
|
| 16 | +--- |
| 17 | + |
| 18 | +## Step 1. Start MySQL with binary logging enabled |
| 19 | + |
| 20 | +```bash |
| 21 | +docker run -d --name mysql-cdc \ |
| 22 | + -e MYSQL_ROOT_PASSWORD=spice \ |
| 23 | + -e MYSQL_DATABASE=spice_demo \ |
| 24 | + -p 3308:3306 \ |
| 25 | + mysql:8.0 \ |
| 26 | + --server-id=1 \ |
| 27 | + --log-bin=mysql-bin \ |
| 28 | + --binlog-format=ROW \ |
| 29 | + --binlog-row-image=FULL |
| 30 | +``` |
| 31 | + |
| 32 | +Wait a few seconds for MySQL to finish initializing, then confirm binary logging is active (`log_bin` should be `ON` and `binlog_format` should be `ROW`): |
| 33 | + |
| 34 | +```bash |
| 35 | +docker exec mysql-cdc mysql -uroot -pspice \ |
| 36 | + -e "SHOW VARIABLES WHERE Variable_name IN ('log_bin','binlog_format','binlog_row_image');" |
| 37 | +``` |
| 38 | + |
| 39 | +```console |
| 40 | ++-------------------+-------+ |
| 41 | +| Variable_name | Value | |
| 42 | ++-------------------+-------+ |
| 43 | +| binlog_format | ROW | |
| 44 | +| binlog_row_image | FULL | |
| 45 | +| log_bin | ON | |
| 46 | ++-------------------+-------+ |
| 47 | +``` |
| 48 | + |
| 49 | +--- |
| 50 | + |
| 51 | +## Step 2. Create a replication user and seed the table |
| 52 | + |
| 53 | +Spice connects with a user that can read the binlog. The minimum privileges are `REPLICATION SLAVE`, `REPLICATION CLIENT`, and `SELECT`. |
| 54 | + |
| 55 | +```bash |
| 56 | +docker exec -i mysql-cdc mysql -uroot -pspice <<'EOF' |
| 57 | +CREATE USER 'spice'@'%' IDENTIFIED BY 'spice'; |
| 58 | +GRANT REPLICATION SLAVE, REPLICATION CLIENT, SELECT ON *.* TO 'spice'@'%'; |
| 59 | +FLUSH PRIVILEGES; |
| 60 | +
|
| 61 | +USE spice_demo; |
| 62 | +CREATE TABLE orders ( |
| 63 | + id BIGINT AUTO_INCREMENT PRIMARY KEY, |
| 64 | + customer VARCHAR(255), |
| 65 | + amount DECIMAL(10,2), |
| 66 | + status VARCHAR(32) |
| 67 | +); |
| 68 | +INSERT INTO orders (customer, amount, status) VALUES |
| 69 | + ('Alice', 99.99, 'pending'), |
| 70 | + ('Bob', 149.50, 'pending'), |
| 71 | + ('Charlie', 299.00, 'shipped'); |
| 72 | +SELECT CONCAT('Seeded ', COUNT(*), ' orders') AS result FROM orders; |
| 73 | +EOF |
| 74 | +``` |
| 75 | + |
| 76 | +--- |
| 77 | + |
| 78 | +## Step 3. Start the Spice runtime |
| 79 | + |
| 80 | +```bash |
| 81 | +spice run |
| 82 | +``` |
| 83 | + |
| 84 | +You should see the dataset bootstrap from a consistent snapshot and then transition to live binlog streaming: |
| 85 | + |
| 86 | +``` |
| 87 | +2025-01-13T12:00:00Z INFO runtime::init::dataset: Initializing dataset orders |
| 88 | +2025-01-13T12:00:00Z INFO runtime::init::dataset: Dataset orders registered (mysql:spice_demo.orders), acceleration (duckdb:file, changes). |
| 89 | +2025-01-13T12:00:00Z INFO runtime::dataconnector::mysql: Bootstrapping MySQL table orders, records=3 |
| 90 | +2025-01-13T12:00:00Z INFO runtime::dataconnector::mysql: Bootstrap complete for orders. Streaming binlog changes. |
| 91 | +2025-01-13T12:00:00Z INFO runtime: All components are loaded. Spice runtime is ready! |
| 92 | +``` |
| 93 | + |
| 94 | +--- |
| 95 | + |
| 96 | +## Step 4. Query the initial snapshot |
| 97 | + |
| 98 | +In a new terminal, open the Spice SQL REPL: |
| 99 | + |
| 100 | +```bash |
| 101 | +spice sql |
| 102 | +``` |
| 103 | + |
| 104 | +```sql |
| 105 | +SELECT * FROM orders; |
| 106 | +``` |
| 107 | + |
| 108 | +```console |
| 109 | ++----+-----------+--------+---------+ |
| 110 | +| id | customer | amount | status | |
| 111 | ++----+-----------+--------+---------+ |
| 112 | +| 1 | Alice | 99.99 | pending | |
| 113 | +| 2 | Bob | 149.50 | pending | |
| 114 | +| 3 | Charlie | 299.00 | shipped | |
| 115 | ++----+-----------+--------+---------+ |
| 116 | + |
| 117 | +Time: 0.008 seconds. 3 rows. |
| 118 | +``` |
| 119 | + |
| 120 | +--- |
| 121 | + |
| 122 | +## Step 5. Insert a record and see it stream |
| 123 | + |
| 124 | +```bash |
| 125 | +docker exec mysql-cdc mysql -uroot -pspice -e \ |
| 126 | + "INSERT INTO spice_demo.orders (customer, amount, status) VALUES ('Diana', 74.00, 'pending');" |
| 127 | +``` |
| 128 | + |
| 129 | +Query again in the SQL REPL: |
| 130 | + |
| 131 | +```sql |
| 132 | +SELECT * FROM orders; |
| 133 | +``` |
| 134 | + |
| 135 | +```console |
| 136 | ++----+-----------+--------+---------+ |
| 137 | +| id | customer | amount | status | |
| 138 | ++----+-----------+--------+---------+ |
| 139 | +| 1 | Alice | 99.99 | pending | |
| 140 | +| 2 | Bob | 149.50 | pending | |
| 141 | +| 3 | Charlie | 299.00 | shipped | |
| 142 | +| 4 | Diana | 74.00 | pending | |
| 143 | ++----+-----------+--------+---------+ |
| 144 | + |
| 145 | +Time: 0.006 seconds. 4 rows. |
| 146 | +``` |
| 147 | + |
| 148 | +--- |
| 149 | + |
| 150 | +## Step 6. Update a record and see the change |
| 151 | + |
| 152 | +```bash |
| 153 | +docker exec mysql-cdc mysql -uroot -pspice -e \ |
| 154 | + "UPDATE spice_demo.orders SET status = 'shipped' WHERE customer = 'Alice';" |
| 155 | +``` |
| 156 | + |
| 157 | +```sql |
| 158 | +SELECT customer, status FROM orders WHERE customer = 'Alice'; |
| 159 | +``` |
| 160 | + |
| 161 | +```console |
| 162 | ++----------+---------+ |
| 163 | +| customer | status | |
| 164 | ++----------+---------+ |
| 165 | +| Alice | shipped | |
| 166 | ++----------+---------+ |
| 167 | + |
| 168 | +Time: 0.005 seconds. 1 rows. |
| 169 | +``` |
| 170 | + |
| 171 | +--- |
| 172 | + |
| 173 | +## Step 7. Delete a record and see it removed |
| 174 | + |
| 175 | +```bash |
| 176 | +docker exec mysql-cdc mysql -uroot -pspice -e \ |
| 177 | + "DELETE FROM spice_demo.orders WHERE customer = 'Bob';" |
| 178 | +``` |
| 179 | + |
| 180 | +```sql |
| 181 | +SELECT * FROM orders; |
| 182 | +``` |
| 183 | + |
| 184 | +```console |
| 185 | ++----+-----------+--------+---------+ |
| 186 | +| id | customer | amount | status | |
| 187 | ++----+-----------+--------+---------+ |
| 188 | +| 1 | Alice | 99.99 | shipped | |
| 189 | +| 3 | Charlie | 299.00 | shipped | |
| 190 | +| 4 | Diana | 74.00 | pending | |
| 191 | ++----+-----------+--------+---------+ |
| 192 | + |
| 193 | +Time: 0.006 seconds. 3 rows. |
| 194 | +``` |
| 195 | + |
| 196 | +--- |
| 197 | + |
| 198 | +## Step 8. Cleanup |
| 199 | + |
| 200 | +```bash |
| 201 | +docker rm -f mysql-cdc |
| 202 | +``` |
| 203 | + |
| 204 | +--- |
| 205 | + |
| 206 | +## Additional Resources |
| 207 | + |
| 208 | +- [MySQL Data Connector documentation](https://docs.spiceai.org/components/data-connectors/mysql) |
| 209 | +- [MySQL Data Connector cookbook](../connector/README.md) |
| 210 | +- [MongoDB Change Streams cookbook](../../mongodb/change-streams/README.md) |
0 commit comments