Skip to main content

VillageSQL is a drop-in replacement for MySQL with extensions.

All examples in this guide work on VillageSQL. Install Now →
MariaDB and MySQL share the same wire protocol and most of the same SQL surface, so a migration looks deceptively simple: dump, then load. In practice, a handful of MariaDB-specific types, objects, and dump behaviors will stop a plain load partway through, and one of them changes a column’s stored type without raising an error at all. This guide covers what actually breaks and how to fix it, based on a real dump taken from MariaDB 12.3.3 and loaded into MySQL.

Data Type and Default Differences

MariaDB-Only Schema Objects Break the Load

A schema that uses CREATE SEQUENCE fails immediately when loaded into MySQL, before any table is created, because mysqldump writes sequence definitions ahead of tables. Loading an unmodified dump containing a sequence produces this, where the at line N values track where the statement sits in your own dump and will not match the numbers shown here:
Remove the DROP SEQUENCE / CREATE SEQUENCE / DO SETVAL(...) block from the dump, and replace any column default that reads DEFAULT nextval(...) with a plain AUTO_INCREMENT column or an application-assigned value. A column typed INET6 fails the same way, later in the same load, once the loader reaches the table that declares it. This is the one error in this guide whose wording depends on which server you load into. VillageSQL names the type, because its grammar accepts custom type names in that position and reports the one it could not resolve:
Stock MySQL 8.4 rejects the same statement with the generic syntax error instead:
Both are ERROR 1064, and both name the offending line, so the fix is the same either way. Change the column type to VARCHAR(45) (long enough for an IPv6 address in text form) before loading, or to an INET type provided by an extension if one is installed on the target.

Foreign Key Constraint Names Collide Across Tables

mysqldump on MariaDB auto-names an unnamed foreign key constraint with a plain integer, starting from 1 again for every table. Two tables in the same schema can both end up with a constraint literally named 1:
MySQL requires foreign key constraint names to be unique across the whole schema, not just within a table. Loading the second CONSTRAINT 1“ fails with:
This only happens when the original tables relied on MariaDB’s auto-naming (no CONSTRAINT name given in the CREATE TABLE). Rename each colliding constraint to something unique, for example order_items_ibfk_1 and orders_ibfk_1, before loading.

The JSON Column Loads Without Error, as a Different Type

Unlike the three issues above, a JSON column loads into MySQL with no error and no warning. The dumped CREATE TABLE for a MariaDB JSON column looks like this:
MySQL supports CHECK constraints and the JSON_VALID() function, so this statement runs as written. The column ends up as LONGTEXT with a CHECK constraint on the MySQL side, not as MySQL’s native JSON type. Reading it back with JSON_EXTRACT() and JSON_LENGTH() gives identical results to the source data, so nothing is lost, but the column’s declared type is different from what a fresh MySQL schema would use. If you want the native JSON type on the target, alter the column after loading:
This leaves the original CHECK (json_valid(...)) constraint attached to the column. The dump does not name that constraint, so the server named it when the CREATE TABLE ran, something like products_chk_1; it is already there before the ALTER. It’s harmless on a native JSON column, since MySQL already rejects invalid JSON at the type level, but it’s dead weight left over from the LONGTEXT definition. Drop it if you want a clean schema:

Authentication Differences

MariaDB and MySQL clients speak the same wire protocol, and a plain password-authenticated connection from one to the other works. What does not carry over is the account definition itself. mysqldump does not dump the mysql.user table by default, so accounts and grants have to be recreated by hand on the target, and the syntax for doing so differs. A MariaDB account created with a password shows up like this:
The same statement on MySQL or VillageSQL, using the default caching_sha2_password plugin, looks like this instead:
Don’t copy a SHOW CREATE USER statement from MariaDB into MySQL. Create the account fresh with CREATE USER ... IDENTIFIED BY '<password>' on the target and let it pick its own default plugin, then reissue the GRANT statements. If you’re testing a migration against a MariaDB install you just set up (a fresh Homebrew install, for example), and a named account with a password gets rejected with “Access denied” even though the password is right, check for anonymous accounts first:
An unsecured MariaDB install can leave ''@'localhost' and ''@'<hostname>' in place. Because host matching is more specific than username matching, a connection that resolves to localhost matches the anonymous account before it matches your named one, and gets rejected because the anonymous account expects no password. This is a MariaDB source-install issue, not something that follows the data into MySQL, but it can make source-side testing confusing until the anonymous accounts are dropped.

Migration Approach

  1. Dump the schema and data with mysqldump (on a MariaDB host this is usually a symlink to MariaDB’s own mariadb-dump; run mysqldump --version to confirm which one you have).
  2. Scan the dump for CREATE SEQUENCE, INET6, and RETURNING before attempting a load. None of the three produce a useful error until the load is already partway through.
  3. Attempt a load into a scratch database first. A CREATE SEQUENCE or INET6 failure aborts the whole script at that statement, so tables defined earlier in the file are already loaded and tables defined later are not. Fix the dump and reload into a fresh database rather than patching a half-loaded one.
  4. Check for foreign key constraint name collisions if the source schema relied on unnamed foreign keys. grep -o 'CONSTRAINT \[0-9]*`’ dump.sql` finds them.
  5. Recreate accounts and grants by hand. mysqldump does not carry them, and the CREATE USER / SHOW CREATE USER syntax differs between the two servers.
  6. Verify row counts and spot-check any JSON columns. A JSON column loads silently as LONGTEXT; confirm the data round-trips with JSON_EXTRACT() before deciding whether to convert it to a native JSON column.

Frequently Asked Questions

Does a MariaDB mysqldump file need any edits for a same-version-family MySQL target?

If the schema doesn’t use sequences, INET6, RETURNING, or unnamed foreign keys, the same dump often loads without changes. Test the load against a scratch database first rather than assuming it will succeed based on the schema being “just tables and data.”

Will a native MySQL JSON column round-trip through a MariaDB dump correctly?

Values round-trip correctly. The column type does not: MariaDB dumps its JSON columns as LONGTEXT with a CHECK constraint, and that is exactly what loads into MySQL, unless you explicitly ALTER TABLE ... MODIFY the column to JSON afterward.

Can I connect to a MariaDB server with a MySQL client, or the reverse?

Yes, for ordinary password-authenticated connections. Both use the same wire protocol, and a client built against one library can authenticate against an account on the other server, once the account itself exists with a password. The SQL each server accepts is where they diverge, not the connection.

Troubleshooting

See also