Migrating MySQL analytics to ClickHouse

A practical guide for teams moving MySQL reporting off transactional databases into self-managed ClickHouse, covering what maps, what does not, and how to run both systems through cutover.

On this page

Moving MySQL analytics to ClickHouse: what the move involves

Moving reporting off MySQL is a data-model and query change plus an ongoing copy, not a lift-and-shift. The reports will not run unchanged, and the size of the effort depends on two things: how many reports you need to move and how fresh the data must be when people read it. Start by inventorying the reports, their joins, their time ranges and their freshness requirements, because those answers decide the copy method, the table design and how long both systems must run side by side. MySQL should keep doing what it does well: serving the transactional application with row-level writes, constraints and short lookups. ClickHouse takes the analytical reads, which scan and aggregate large column sets over wide time ranges. That split means MySQL remains the system of record for the application, and ClickHouse holds a derived analytical copy that you refresh from MySQL. Confirm with your application owners which tables are append-only events and which are mutable records, since updated and deleted rows need a different loading and deduplication approach than inserts alone. The first build tasks are the initial load through the mysql table function, a type mapping from each MySQL column to a ClickHouse type, and a table engine and ordering key chosen for the queries you actually run. Keep MySQL's read load in mind while you copy, and test the load on a non-production copy with representative data before scheduling it against production. Plan a parallel run where both systems answer the same reports, and compare results before you retire any MySQL reporting path.

Which MySQL analytics features map to ClickHouse and which do not

This table compares the MySQL features reporting teams depend on with their ClickHouse equivalents, and marks where the pattern changes. Read it before designing the target schema, since most differences come from the table engine and ordering key rather than from SQL syntax. The main takeaway is that ClickHouse answers analytical reads well, but row-level updates, triggers and procedural logic must move into the pipeline or the application.

FeatureMySQL reporting useClickHouse equivalent and change
IndexesB-tree indexes on filter and join columns for fast point lookupsOrdering key (ORDER BY) defines physical layout; choose it from the most common filter columns, and add skip indexes only after measuring
JoinsJoins between normalized reporting tables, usually small on the right sideSupported, but large joins need planning; denormalize hot reporting tables or pre-join in a materialized view and confirm memory limits
UPDATE and DELETERow-level changes applied in place by reporting-side correctionsUse ReplacingMergeTree or CollapsingMergeTree for updated rows, lightweight deletes or mutations for corrections, and confirm cost on your volumes
TransactionsMulti-statement commits that keep reports consistentNo multi-statement transactions as a reporting model; load complete batches and let readers accept eventual consistency during the load window
ViewsLogical views over joined tables used by report queriesViews are supported; materialized views write aggregates or transformed rows at insert time and suit recurring dashboards
Stored proceduresBusiness logic and aggregation executed inside the databaseNot supported; move the logic into the extraction pipeline, a transformation job or the application layer
TriggersAutomatic row changes on insert or update in MySQLNot supported; replace with materialized views on the ClickHouse side or with change capture logic in the pipeline
Data typesDECIMAL, DATETIME, unsigned integers and ENUM with MySQL semanticsMap to Decimal, DateTime64, UInt types and LowCardinality(String) explicitly; check nullability and precision with a sample load

Documented ways to copy MySQL data into ClickHouse

ClickHouse offers several documented ways to pull MySQL data in, and each one places load, credentials and network trust in a different spot. The cards compare the table function, the table and database engines and an ETL host, then cover the access and version checks that apply to all of them.

The mysql table function

The mysql() table function runs inside a ClickHouse query and reads rows from a MySQL table or query over the network. Teams use it for the initial copy and for scheduled pull-based loads with INSERT INTO ... SELECT. Each run scans the source, so large tables mean long reads against production MySQL. Confirm that the ClickHouse nodes can reach the MySQL port, and that a re-run of the load cannot duplicate rows in the target.

The MySQL table engine

The MySQL table engine creates a ClickHouse table that forwards reads to one MySQL table at query time, so no data is stored in ClickHouse. It suits spot checks, row count reconciliation and joins against small reference tables. It is a poor fit for heavy reporting, because every query hits the live source and adds load to it. Confirm which filters are pushed down to MySQL in your version, since unpushed filters turn into full reads of the source table.

The MySQL database engine

The MySQL database engine maps an entire MySQL schema into ClickHouse, so its tables appear as a ClickHouse database without per-table definitions. This helps when exploring a schema, but it exposes every table in that schema to anyone holding the matching ClickHouse grant. Source-side column changes can alter what reporting queries return, so confirm how your version handles them. Scope the MySQL account to the tables you intend to expose, and rerun key reports after each source change.

clickhouse-local on a separate host

Running clickhouse-local on a separate host lets that machine read from MySQL and push rows into the cluster, so the cluster never needs a route into the source network. The staging host holds the MySQL credentials and the data in transit, so it needs the same access controls as the source. One host also caps throughput and becomes a single point of failure for scheduled loads. Use TLS for the push into the cluster, and keep the host's network path limited to those two endpoints.

Least-privilege MySQL access

Every path above authenticates to MySQL, and the table function takes the password inline in the SQL text, where it can reach query logs and shared sessions. Create a dedicated MySQL account with SELECT on the reporting schema only, and enforce TLS for connections from ClickHouse. Check whether your version supports stored named collections for credentials, and confirm that log masking covers the query text before you rely on it.

Confirm behavior for your version

Connector behavior, filter pushdown and type handling can change between ClickHouse releases, so check the documentation for the version you run before relying on any older guide. ClickHouse recommends stable releases for production, a pre-production environment with representative data and queries, and a changelog review before each upgrade. Test every path on a copy of your MySQL data, and record row counts and sample values before any cutover.

How the initial copy and ongoing sync fit together

The migration has two phases that should be designed as one pipeline: a bulk historical load that copies existing reporting tables, then a repeatable incremental pull that carries changes from MySQL into ClickHouse until cutover. The bulk load usually runs with the mysql table function or a clickhouse-local job, and it should record the highest value of a monotonic key or timestamp it read, such as an auto-increment id or an updated_at column, so the incremental phase knows where to resume. Confirm with your DBA that the chosen column is indexed on the source and that every writer updates it. A timestamp that a batch job sets late, or a sequence that skips values after a failover, will silently drop rows.

Deletes and late-arriving rows need explicit handling because a timestamp pull cannot see them by default. For deletes, decide whether the source soft-deletes rows (a flag or tombstone column that the pull can read) or hard-deletes them, and if hard deletes occur, plan a periodic reconciliation that compares key ranges or counts between systems. Late rows, where updated_at is older than the last watermark, call for an overlap window: re-read a short range behind the watermark and let the target absorb duplicates. Test both paths with a deliberate delete and a backdated update in a staging copy before you rely on them for reports.

For updated rows, ReplacingMergeTree is a common target because it keeps the row with the highest version value for each sorting key during background merges, so re-inserting a changed row with a higher updated_at or version column is a normal pattern. The catch is that merges are eventual, so queries that need the current state should use FINAL or be written to pick the latest version explicitly, and the team should measure the cost of that on realistic table sizes. Whether this pattern fits depends on the source's update behavior: append-only event tables may need no versioning at all, while mutable dimension tables with frequent updates and deletes need the versioned design. Choose the table engine per table after reviewing how the MySQL application writes to each one.

The migration sequence from inventory to decommission

The sequence turns an unplanned move of reporting queries into a staged process in which each report is inventoried, proven on ClickHouse, compared against MySQL, switched over and only then retired. It ends with the MySQL reporting path removed and the ClickHouse backup and restore path proven.

  1. Inventory every report and its freshness need

    List each dashboard, scheduled export, API endpoint and ad hoc query that reads from MySQL for reporting, with its owner, its consumers, the tables and columns it touches, and how stale its results may be. Classify each as near real time, hourly, daily or periodic, because that classification decides which replication method each table needs. The step is done when every report has an owner, a freshness requirement and a row in an agreed inventory that the business has signed off.

  2. Pilot on representative data and queries

    Load a subset of the reporting tables into a pre-production ClickHouse cluster using the mysql table function, then map MySQL types to ClickHouse types and rewrite the highest-value queries. Use real data volumes and the actual query patterns from the inventory, not a small sample, so that ordering keys and performance can be judged. The step closes when the pilot reports run on representative data and the team has a written list of type mappings, rewritten queries and unresolved gaps.

  3. Establish a restore test before cutover

    Before any consumer depends on ClickHouse, take a BACKUP of the pilot and production-candidate databases to the chosen target, restore it onto a spare cluster and confirm that row counts, key aggregates and sample reports match. ClickHouse documentation notes that replication does not protect against human error or corruption, and a backup that has never been restored may not restore when needed. The step is done when a restore has been completed, verified and recorded with its date and result.

  4. Run a parallel load with result comparison

    Keep MySQL as the system of record while the synchronization job keeps ClickHouse current for the agreed freshness window. Run each rewritten query against both systems on the same schedule, and compare totals, group counts, distinct keys and time-bucketed figures, recording every mismatch with its cause. The step closes when comparisons have stayed within the documented tolerance for the agreed period, and any differences are either fixed or explained and accepted by the report owners.

  5. Cut over report consumers in stages

    Move consumers group by group, starting with the reports of lowest business risk, by pointing dashboards, exports and APIs at ClickHouse while keeping the MySQL version available for rollback. Set access controls for the ClickHouse users behind each consumer, avoid using the default user, and confirm each consumer's permissions match what it had in MySQL. The step is done when every consumer in the inventory reads from ClickHouse and the owners have confirmed their outputs in writing.

  6. Retire the MySQL reporting path

    After a stable period with no unresolved comparison failures, stop the synchronization job, remove the reporting queries and scheduled exports that targeted MySQL, and revoke the reporting accounts. Keep the final MySQL backups and the ClickHouse backup schedule in place according to the retention policy agreed with the data owners. The step closes when the MySQL reporting path is no longer referenced by any consumer, the accounts are revoked and the decommission is recorded.

  7. Hand over operations and document the result

    Transfer monitoring, backup schedules, restore test cadence, upgrade ownership and the runbook for the synchronization job to the team that will operate ClickHouse. Record the final inventory, type mappings, rewritten queries and comparison results so that future changes can be checked against them. The step is complete when the operating team has run the runbook at least once and the documentation is stored where it can be reviewed by auditors and future engineers.

Rewriting reporting queries and validating results

The biggest source of wrong numbers after a MySQL to ClickHouse move is not the copy itself but query semantics that look the same and behave differently. Joins are the first thing to re-examine. ClickHouse keeps the right-hand table of a JOIN in memory as a hash table, so a large dimension on the right side can exhaust memory while a small one works well. Reporting queries that joined many normalized MySQL tables often need to be reshaped: put the largest fact table on the left, pre-join slowly changing dimensions into a flat table, or use a dictionary for small lookups. Confirm the join algorithm and memory limits your target version exposes before you rewrite, and test the heaviest reports at production-sized data rather than a sample.

Aggregations and date handling need the same scrutiny. Queries that group by a MySQL timestamp column should be rewritten to filter on the table's ordering key, typically a date or timestamp column combined with a tenant or source identifier, so ClickHouse can skip granules instead of scanning whole parts. Check every date function as well: time zone handling, the first day of a week, and how NULL dates sort can differ from MySQL, so review each reporting period boundary explicitly. For the validation step, run the same report on both systems over identical, closed date windows, for example a fixed month that no longer receives writes, and compare row counts, sums and distinct counts per group. Agree tolerances with the report owners before the comparison starts. Floating point totals may differ in the last decimal places, and the copy may lag on the open window, so a documented exception list is more useful than a demand for exact equality.

What drives the cost and duration of a MySQL to ClickHouse move

This table lists the factors that raise or lower the effort of moving MySQL reporting workloads to self-managed ClickHouse, and what to request in a written quote for each. Read it before asking partners for a fixed-scope proposal, because the same workload can differ widely in effort depending on these factors. No duration figures are given here, since none were supplied for this topic.

DriverEffect on effortWhy it mattersWhat to ask for in a quote
Number and complexity of reportsRaisesEach report needs its joins, filters and aggregations rewritten and its results compared against MySQL output.A list of in-scope reports with complexity notes and a named result-comparison method for each.
Data volume and history depthRaisesLarge initial copies take longer to load and verify, and historical backfills multiply validation work.The load method, the rows and tables covered, and how the initial copy will be checked.
Update and delete frequencyRaisesRows changed or removed in MySQL after the copy need an ongoing sync method, which is harder to get right than append-only data.How changes will be captured or re-synced, and how deletes are represented in ClickHouse.
Freshness requirementRaises when near real timeHourly or daily refresh is simpler than continuous replication with tight lag targets.The agreed data lag, the refresh schedule and the monitoring for lag.
Type and schema complexityRaisesMySQL types such as DECIMAL precision, ENUM, SET, JSON and nullable columns need explicit mapping choices, and schemas with many nullable or wide columns need more review.A type mapping table for every in-scope table and a list of columns needing a decision.
Number of consumers and BI toolsRaisesEvery dashboard, extract and downstream job that reads from MySQL must be repointed and tested against ClickHouse.An inventory of consumers, the BI tools in use, and which of them are included in the scope.
Query dialect differencesRaisesFunctions, date handling and join patterns that work in MySQL may need different SQL in ClickHouse.A count of queries to rewrite and the approach for flagging unsupported constructs.
Security and network constraintsRaisesRestricted networks, certificate requirements and separate credentials for the source and target add setup and approval steps.The network paths to be opened, the credential model, and who approves firewall or TLS changes.
Depth of parallel-run validationRaises with depth, lowers riskRunning both systems side by side for a defined period and comparing outputs takes more time but catches discrepancies before cutover.The validation plan, the comparison criteria, the length of the parallel run and the sign-off owner.

What usually goes wrong in MySQL to ClickHouse moves

Most failed MySQL to ClickHouse moves fail quietly rather than loudly. The copy finishes, the dashboards render, and the numbers are slightly wrong, or the one path that was never tested fails during an incident. The cards below cover the risks that show up most often and the checks that catch them before cutover.

Silent type mismatches

Decimals, timestamps and time zones often convert without an error. A DECIMAL column loaded as a Float64 loses precision on large money values, and DATETIME values carry no zone while TIMESTAMP values are stored in UTC, so a naive copy can shift every hour-level report. Prevention: define target column types explicitly instead of accepting inferred ones, then compare sums, counts and min and max values per day between MySQL and ClickHouse on a sample of real rows.

Missed deletes

Rows deleted in MySQL can remain in ClickHouse indefinitely. An initial copy and periodic appends capture inserts and updates but not removals, so reports drift upward over time. Prevention: decide how deletes arrive (a delete flag from the application, binlog-based capture, or periodic key reconciliation), and run a scheduled comparison of primary key counts between the two systems.

Incomplete incremental keys

A watermark column that is not monotonic or not indexed misses rows. An updated_at column set by the application can move backward, be null on older rows, or skip rows written in the same second as the last sync. Prevention: check how the watermark is written across every service that touches the table, add an overlap window on each incremental pull, and make the load idempotent by keying on the primary key.

Source load from full scans

Repeated full-table reads can slow the production MySQL primary. The mysql table function reads through the source, so a large copy or a poorly filtered incremental query competes with transactional traffic during business hours. Prevention: run bulk copies against a replica where possible, filter by indexed ranges in chunks, schedule the heavy passes off-peak, and watch replication lag while they run.

Queries that assume row indexes

Reports written for MySQL secondary indexes can scan far more data in ClickHouse. A query that filters on a single customer ID is fast in InnoDB but may read most of a table if that column is not in the ordering key. Prevention: review the top reporting queries before copying the schema, choose ORDER BY keys from those filters, and compare query plans and runtimes on representative data.

Cutover without a rollback path

Switching all reports at once leaves no safe way back if results disagree. Teams often retire the MySQL reporting queries as soon as ClickHouse looks correct, then discover a discrepancy weeks later. Prevention: run both systems in parallel for an agreed period, keep the MySQL reporting path switchable by configuration, and document who signs off on the comparison before the old path is retired.

How much effort does a MySQL to ClickHouse migration take?

What drives the cost of a MySQL to ClickHouse migration?

The biggest cost drivers are the number of reports to move, how many MySQL tables feed them, how much history must be copied, and how many query rewrites the reports need. Cost also rises with the number of environments, the security and access model you need, and how much ongoing change MySQL receives during the project. A fixed-scope quote follows from a written scope that lists the reports, source tables, data volume and acceptance criteria. Ask for the cost of each component separately: discovery, initial load, change capture, query rewrites, validation, cutover support and handover. Your own staff time is also a cost, and it is easy to miss in a comparison.

What is one-time work and what recurs after a MySQL to ClickHouse move?

Most of the migration is one-time: mapping types, building the initial copy, designing ClickHouse table layouts, rewriting queries and validating results. Recurring work starts after cutover and includes running the ClickHouse cluster, applying ClickHouse upgrades, keeping backups tested, monitoring replicas and maintaining the pipeline that keeps data current. If you keep a MySQL reporting replica during a parallel run, that database and its licenses also continue to cost money until you retire it. Ask any partner to separate one-time project fees from support or retainer terms so you can compare the two models clearly.

How long does a MySQL to ClickHouse migration take?

Duration depends on the same factors as cost: how many reports and source tables are in scope, how large the historical data is, how fast the data changes, how many queries need rewriting, and how quickly your team can review results. Your internal review time often sets the pace more than the technical work does. No general duration applies to every environment, so ask for a timeline built from the written scope, with named milestones for the initial copy, parallel run and cutover. Treat any timeline without those milestones as incomplete.

What must our internal team provide during the migration?

Your team needs to provide clear owners for three areas. Schema owners confirm the meaning, keys, nullability and history rules for each MySQL table. Report owners define what each dashboard or report must return, including edge cases such as time zones, deleted rows and rounding. A sign-off owner accepts the parallel-run results and approves cutover. You also need database administrators for MySQL access and change windows, network staff for connectivity from the migration host, and security staff for user, role and certificate decisions. Without these people available on a fixed schedule, the project stalls at review points.

Can our team run a MySQL to ClickHouse migration without outside help?

A team with strong SQL, MySQL administration and ClickHouse operations experience can do the work alone, especially for a small number of reports and a source that changes slowly. The risk rises with the number of tables, the volume of history, the rate of change in MySQL, and the number of reports that depend on exact figures. Before deciding, ask whether anyone has run a ClickHouse replicated cluster, restored from a tested backup and compared results between two systems. If those skills are thin, a fixed-scope engagement can cover the gaps while your team keeps ownership of the platform.

Is ClickHouse licensed under Apache 2.0 and is MySQL Community Edition under GPL?

ClickHouse server is open source under the Apache License 2.0 (SPDX identifier Apache-2.0), according to its GitHub repository. ClickHouse Inc. separately sells ClickHouse Cloud as a managed service, which is a different commercial offering. MySQL Community Edition is the freely downloadable version of MySQL and is available under the GPL license, according to mysql.com. MySQL Enterprise Edition is a separate commercial product with added features such as Enterprise Backup and Transparent Data Encryption. Have your legal or procurement team review the license terms for the exact versions and any bundled components you plan to deploy.

Which MySQL release lines are supported, and which should we use as the source?

MySQL has an LTS track and an Innovation track. Under the Oracle Lifetime Support Policy, LTS releases receive 5 years of premier support and 3 years of extended support, while Innovation releases are supported only until the next Innovation release, according to the MySQL 8.4 reference manual. The MySQL Community Server download page currently lists 26.7.0 (Innovation), 9.7.2 LTS, 8.4.11 LTS and 8.0.46. For a source database, prefer a line that still has active support and confirm its end-of-life date on the MySQL release page before you plan the work. Your MySQL version affects the binlog and replication options available for change capture.

How do we confirm which ClickHouse version is supported before we choose one?

Check the ClickHouse security policy on GitHub, which lists the versions currently receiving security updates. At the time of research, it listed 26.9, 26.8, 26.7 and 26.3 as supported, and all other versions as unsupported. Also review the GitHub releases page, which showed v26.9.20.23-stable as the latest stable release and the LTS lines v26.8.29.10-lts and v26.3.53.4-lts, both published on October 16, 2026. Because these lists change, confirm them on the day you decide. The ClickHouse production FAQ recommends stable releases for most production use and reading the changelog before any upgrade.

How DeployOpen scopes and delivers the migration

DeployOpen plans, builds, tests and hands over MySQL to ClickHouse reporting migrations inside infrastructure you control, under a written scope agreed before work starts. The work covers the initial copy, ongoing data sync, type mapping, query rewrites and the parallel-run cutover plan.

How we scope

  • Inventory the MySQL reports, dashboards and queries that move, and rank them by business use and data volume.
  • Agree in writing which tables, history depth, refresh latency and cutover criteria the migration covers.
  • Confirm the MySQL version, the target ClickHouse version and the network path between the two systems.

How we build

  • Build the initial copy and sync path on a non-production cluster, then test row counts and checksums against MySQL.
  • Map MySQL column types to ClickHouse types and document every case where values change shape.
  • Rewrite priority queries and compare results with the MySQL originals before any dashboard is repointed.

How we hand over

  • Deliver runbooks for the sync job, the cutover steps, rollback and the parallel-run comparison checks.
  • Document schema decisions, ordering keys, access roles and backup settings so your team can operate the cluster.
  • Walk your engineers through a restore test and a dashboard switch before the MySQL reporting path is retired.

How we support

  • Scope post-cutover support as a retainer covering monitoring reviews, upgrades and query tuning when needed.
  • Plan ClickHouse version upgrades against the release channel and changelog you choose to follow.
  • Agree support hours, response expectations and escalation paths in the written scope rather than assuming them.

Talk to our team.

Tell us what you're working on, whether it's a deployment, an audit, a security test or a cyber range. You'll speak with an engineer who can help you scope it.

  • 30-minute call: free, with no obligation.
  • NDA on request: we can sign before you share details.
  • Clear next steps: a scope and plan after the call.