Skip to content

MySQL vs MariaDB

12 min readMySQLMariaDBDatabases

Short version: either one will run a standard web application without complaint, so pick on the specifics. MySQL gives you the broader ecosystem, managed hosting on every provider, and multi-valued JSON indexes. MariaDB gives you system-versioned tables, sequences, thread pooling without an Enterprise license, and a plain GPL v2 with no proprietary edition above it. They still share a wire protocol, so this is one of the least permanent database decisions you will make.

Your new project needs a relational database and you've settled on the MySQL family. Maybe your framework defaults to it, maybe your hosting provider supports it, maybe you've just used it before and it works. Then someone on your team asks: "Why not MariaDB? It's the same thing, right?"

It's not quite the same thing. MariaDB forked from MySQL in 2009 after Oracle acquired Sun Microsystems, and the two have been diverging ever since. MariaDB added sequences and system-versioned tables. MySQL doubled down on JSON support and enterprise tooling. They share a wire protocol and most of the SQL syntax, but they make different tradeoffs that start to matter once your project grows past a prototype.

This post puts both side by side with runnable code so you can see exactly where they agree and where they don't.

Contents

Quick Comparison

MySQLMariaDB
LicenseGPL v2 + proprietary (Oracle)GPL v2 (MariaDB Foundation)
GovernanceOracle CorporationMariaDB Foundation (community)
SequencesNo (use AUTO_INCREMENT workarounds)Yes, first-class CREATE SEQUENCE
System-versioned tablesNoYes, built-in temporal queries
JSON supportStrong (JSON_TABLE, multi-valued indexes on JSON arrays)JSON_TABLE since 10.6, no multi-valued indexes
Default storage engineInnoDBInnoDB (also ships Aria, ColumnStore)
Oracle compatibility modeNoYes (SET SQL_MODE='ORACLE')
Wire protocolMySQL protocolMySQL protocol (compatible)
Node.js drivermysql2mariadb (official connector)
Cloud availabilityEvery major provider (AWS RDS, GCP Cloud SQL, Azure)AWS RDS, plus first-party MariaDB Cloud since 2025

The shared foundation means most ORMs, admin tools, and hosting providers treat them interchangeably. The differences emerge when you need a specific feature from one side.

Set Up Both Locally

We'll run both side by side using the Layerbase CLI (formerly SpinDB). No Docker, no manual installs. (What is the Layerbase CLI?)

Install the Layerbase CLI:

bash
npm i -g layerbase    # npm
pnpm add -g layerbase # pnpm

Create both instances. The Layerbase CLI assigns non-conflicting ports automatically:

bash
lbase create mysql1 -e mysql --start
lbase create maria1 -e mariadb --start

Verify they're running:

bash
lbase url mysql1
lbase url maria1
text
mysql://root@127.0.0.1:3306
mysql://root@127.0.0.1:3307/test

Your ports may differ. The Layerbase CLI picks the next available port if the default is taken.

Or skip local setup entirely and spin up managed instances on Layerbase Cloud: MySQL or MariaDB.

Project Setup

MySQL and MariaDB have separate Node.js packages, so we need both drivers:

bash
mkdir mysql-vs-mariadb && cd mysql-vs-mariadb
pnpm init
pnpm add mysql2 mariadb
pnpm add -D tsx typescript

Create a file called compare.ts. All the code in this post goes into that file.

Side-by-Side: The Basics

Connect to both, create the same table, insert data, query it. The SQL is nearly identical; the driver APIs differ slightly.

typescript
import mysql from 'mysql2/promise'
import mariadb from 'mariadb'

// Connect to both
const my = await mysql.createConnection({
  host: '127.0.0.1',
  port: 3306,
  user: 'root',
  database: 'test',
})

const maria = await mariadb.createConnection({
  host: '127.0.0.1',
  port: 3307,
  user: 'root',
  database: 'test',
})

console.log('Connected to MySQL and MariaDB')

Now create identical tables on both:

typescript
const createProducts = `
  CREATE TABLE IF NOT EXISTS products (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    sku VARCHAR(50) NOT NULL UNIQUE,
    price DECIMAL(10, 2) NOT NULL,
    category VARCHAR(100) NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
  )
`

// Clean up from previous runs
await my.execute('DROP TABLE IF EXISTS products')
await maria.query('DROP TABLE IF EXISTS products')

await my.execute(createProducts)
await maria.query(createProducts)

console.log('Created products table on both databases')

One driver difference to note: mysql2 uses .execute() for parameterized queries, while the mariadb connector uses .query() for everything. Both support ? placeholders.

Insert the same data into both:

typescript
const products = [
  { name: 'Mechanical Keyboard', sku: 'KB-001', price: 129.99, category: 'peripherals' },
  { name: '27" 4K Monitor', sku: 'MN-001', price: 449.99, category: 'displays' },
  { name: 'Standing Desk', sku: 'DK-001', price: 599.99, category: 'furniture' },
  { name: 'Wireless Mouse', sku: 'MS-001', price: 39.99, category: 'peripherals' },
  { name: 'Noise-Canceling Headphones', sku: 'HP-001', price: 249.99, category: 'audio' },
]

for (const p of products) {
  const params = [p.name, p.sku, p.price, p.category]
  const sql = 'INSERT INTO products (name, sku, price, category) VALUES (?, ?, ?, ?)'
  await my.execute(sql, params)
  await maria.query(sql, params)
}

console.log(`Inserted ${products.length} products into both databases`)

Query both and compare results:

typescript
const myProducts = (await my.execute(
  'SELECT name, price, category FROM products ORDER BY price DESC',
))[0] as Array<{ name: string; price: number; category: string }>

const mariaProducts = await maria.query(
  'SELECT name, price, category FROM products ORDER BY price DESC',
)

console.log('\nMySQL results:')
for (const row of myProducts) {
  console.log(`  ${row.name}: $${Number(row.price).toFixed(2)}`)
}

console.log('\nMariaDB results:')
for (const row of mariaProducts) {
  console.log(`  ${row.name}: $${Number(row.price).toFixed(2)}`)
}
text
MySQL results:
  Standing Desk: $599.99
  27" 4K Monitor: $449.99
  Noise-Canceling Headphones: $249.99
  Mechanical Keyboard: $129.99
  Wireless Mouse: $39.99

MariaDB results:
  Standing Desk: $599.99
  27" 4K Monitor: $449.99
  Noise-Canceling Headphones: $249.99
  Mechanical Keyboard: $129.99
  Wireless Mouse: $39.99

Identical output. For basic CRUD, JOINs, and aggregations, they're interchangeable. The differences show up when you reach for more advanced features.

What MariaDB Has That MySQL Doesn't

Sequences

MySQL limits you to AUTO_INCREMENT on a single column per table. MariaDB has standalone sequences: named counters that live independently of any table. Useful for order numbers, invoice IDs, or any sequential value shared across tables.

typescript
await maria.query('DROP SEQUENCE IF EXISTS invoice_seq')

await maria.query(`
  CREATE SEQUENCE invoice_seq
    START WITH 5000
    INCREMENT BY 1
`)

const invoiceNumbers = []
for (let i = 0; i < 4; i++) {
  const [row] = await maria.query('SELECT NEXT VALUE FOR invoice_seq AS num')
  invoiceNumbers.push(row.num)
}

console.log('\nMariaDB sequence-generated invoice numbers:', invoiceNumbers)
text
MariaDB sequence-generated invoice numbers: [ 5000, 5001, 5002, 5003 ]

In MySQL, you'd create a dedicated single-row table, lock it with SELECT ... FOR UPDATE, increment, and commit. A lot of ceremony for something that should be trivial.

System-Versioned Tables

This is MariaDB's strongest differentiator, and I think it's genuinely underrated. System versioning lets the database automatically track every historical version of every row. No triggers, no audit tables, no application code.

typescript
await maria.query('ALTER TABLE products ADD SYSTEM VERSIONING')

// Record the timestamp before changes
const [{ now: beforeChanges }] = await maria.query('SELECT NOW(6) AS now')

// Simulate a price increase on peripherals
await maria.query(`UPDATE products SET price = price * 1.15 WHERE category = 'peripherals'`)

// Query the table as it was before the update
const before = await maria.query(
  `SELECT name, price FROM products FOR SYSTEM_TIME AS OF ?
   WHERE category = 'peripherals' ORDER BY name`,
  [beforeChanges],
)

const after = await maria.query(
  `SELECT name, price FROM products WHERE category = 'peripherals' ORDER BY name`,
)

console.log('\nMariaDB temporal query - peripherals before price change:')
for (const row of before) {
  console.log(`  ${row.name}: $${Number(row.price).toFixed(2)}`)
}

console.log('\nPeripherals after price change:')
for (const row of after) {
  console.log(`  ${row.name}: $${Number(row.price).toFixed(2)}`)
}
text
MariaDB temporal query - peripherals before price change:
  Mechanical Keyboard: $129.99
  Wireless Mouse: $39.99

Peripherals after price change:
  Mechanical Keyboard: $149.49
  Wireless Mouse: $45.99

The FOR SYSTEM_TIME AS OF clause lets you query any point in the past. MySQL has no equivalent. You'd have to build a separate audit table, write triggers on every INSERT/UPDATE/DELETE, and write custom queries to reconstruct past states. MariaDB gives you this with a single ALTER TABLE statement.

This matters for compliance (GDPR audit trails, financial record-keeping), debugging ("what did the config look like yesterday at 3pm?"), and recovery ("someone deleted the wrong rows, can we get them back?"). I've seen teams spend weeks building audit infrastructure that MariaDB gives you for free.

What MySQL Has That MariaDB Doesn't

Advanced JSON Operations

Both support JSON columns and both have JSON_TABLE, which MariaDB added in 10.6. Where MySQL still pulls ahead is the indexing and validation layer around JSON rather than the query functions themselves.

JSON_TABLE converts JSON arrays into relational rows you can JOIN against. This example is MySQL, and the same query works on MariaDB 10.6 and later:

typescript
await my.execute('DROP TABLE IF EXISTS orders')

await my.execute(`
  CREATE TABLE orders (
    id INT AUTO_INCREMENT PRIMARY KEY,
    customer VARCHAR(100) NOT NULL,
    items JSON NOT NULL
  )
`)

await my.execute(
  `INSERT INTO orders (customer, items) VALUES
   ('Alice', '["KB-001", "MS-001"]'),
   ('Bob', '["MN-001", "DK-001", "HP-001"]')`,
)

const [orderDetails] = await my.execute(`
  SELECT
    o.customer,
    jt.sku,
    p.name,
    p.price
  FROM orders o,
  JSON_TABLE(o.items, '$[*]' COLUMNS (sku VARCHAR(50) PATH '$')) AS jt
  JOIN products p ON p.sku = jt.sku
  ORDER BY o.customer, p.price DESC
`)

console.log('\nMySQL JSON_TABLE - orders with product details:')
for (const row of orderDetails as Array<{ customer: string; sku: string; name: string; price: number }>) {
  console.log(`  ${row.customer}: ${row.name} ($${Number(row.price).toFixed(2)})`)
}
text
MySQL JSON_TABLE - orders with product details:
  Alice: Mechanical Keyboard ($129.99)
  Alice: Wireless Mouse ($39.99)
  Bob: Standing Desk ($599.99)
  Bob: 27" 4K Monitor ($449.99)
  Bob: Noise-Canceling Headphones ($249.99)

The real MySQL advantage is multi-valued indexes on JSON arrays, which index the individual elements so a lookup into an array does not scan the table. MariaDB has no equivalent, so at scale you either normalize those arrays into a proper relational table or accept the scan. Honestly, normalization is often the better design anyway, but sometimes you need the flexibility.

(JSON claims re-checked 2026-08-25 against the MariaDB JSON_TABLE reference, which states JSON_TABLE is available from MariaDB 10.6. An earlier version of this post said MariaDB lacked it until 11.2, which was wrong.)

Licensing and Governance

This is where opinions get strong.

MySQL is dual-licensed: GPL v2 for the community edition, proprietary for enterprise. Oracle controls the roadmap, the release schedule, and which features land in community vs. enterprise. Community MySQL is fully functional, but Thread Pool, audit logging, and MySQL Router for high availability are paid.

MariaDB is GPL v2, period. The MariaDB Foundation oversees it as a genuine open-source effort. No proprietary fork with hidden features. The MariaDB Corporation (a separate entity) sells commercial support and the MaxScale proxy, but the database itself is fully open.

If your organization cares about software freedom or long-term license stability, MariaDB's governance model is more predictable. If you want Oracle-backed enterprise support and don't mind the dual-license model, MySQL Enterprise is a polished product.

Performance

For most workloads, performance shouldn't be your deciding factor. Both use InnoDB. Both handle concurrent reads and writes well. Both support connection pooling, query caching, and the usual tuning knobs.

Where they diverge:

  • Thread pool: MySQL gates this behind Enterprise. MariaDB includes it in the community edition, which can improve performance under high concurrency
  • Optimizer: MariaDB's query optimizer has diverged from MySQL's and sometimes produces different execution plans. In some benchmarks MariaDB is faster for complex JOINs; in others MySQL wins on simple lookups. Benchmark your actual queries
  • Storage engines: MariaDB ships Aria (crash-safe MyISAM replacement) and ColumnStore (for analytics). These don't affect InnoDB performance but give you more options for specialized workloads

Ecosystem and Hosting

MySQL wins on ecosystem breadth. It's not particularly close.

Every major cloud provider offers managed MySQL: AWS RDS, Google Cloud SQL, Azure Database for MySQL, DigitalOcean, PlanetScale. MariaDB is available on AWS RDS but not as universally across the other hyperscalers.

The first-party picture changed in 2025, though. MariaDB plc reacquired SkySQL in August 2025 and relaunched the managed offering as MariaDB Cloud, so there is now a self-serve managed MariaDB from the company that makes the database, which was not true when this post first went up. It is still a smaller footprint than managed MySQL, which runs on essentially every provider you might already be using. (Verified 2026-08-25 against mariadb.com/products/cloud.)

WordPress, Drupal, Magento, and most PHP-era CMSes were built on MySQL. They generally work with MariaDB (many Linux distributions have actually switched their default to MariaDB), but MySQL is what the documentation targets.

ORMs and database tools support both through the shared wire protocol. Prisma, Drizzle, TypeORM, Sequelize, Knex, TablePlus: they all connect to either one. You rarely need to change application code when switching.

Layerbase Cloud supports both MySQL and MariaDB as managed instances with TLS, so you can try either one without committing to a local install. It can also bring an existing database in: pick Migrating from another platform in the create flow and paste a MySQL/MariaDB connection string (it runs mysqldump once and copies schema + data), or, if you're leaving PlanetScale, paste a service token and it pulls the database across for you.

When to Pick MySQL

  • Your hosting provider offers managed MySQL but not MariaDB. Don't fight your platform.
  • You lean heavily on JSON. JSON_TABLE, multi-valued indexes, and JSON_SCHEMA_VALID are genuinely useful if your schema mixes relational and document data.
  • You want Oracle-backed enterprise support. MySQL Enterprise includes commercial SLAs, Oracle Cloud integration, and HeatWave analytics.
  • Your existing stack assumes MySQL. WordPress, Laravel Forge, Vitess: dozens of tools are MySQL-first. If you're deep in that ecosystem, stick with it.
  • You value the largest possible hiring pool. More developers have MySQL on their resume than MariaDB.

When to Pick MariaDB

  • You need temporal data. System-versioned tables alone justify MariaDB for audit-heavy applications. Building the equivalent in MySQL takes significant custom infrastructure.
  • You want sequences. CREATE SEQUENCE beats the MySQL workaround of a dedicated table with row-level locking. Not even close.
  • Open-source governance matters. If you need certainty that your database will stay fully open, MariaDB's Foundation model provides that.
  • You're migrating from Oracle DB. Oracle compatibility mode (SET SQL_MODE='ORACLE') eases the transition.
  • You want thread pooling without paying for Enterprise. MariaDB includes it out of the box.

The Verdict

If you're starting a new project and don't have specific requirements pulling you one way, here's the practical answer:

Pick MySQL if you want the largest ecosystem, the most managed hosting options, and strong JSON support. It's the safe default with the widest safety net.

Pick MariaDB if you need system-versioned tables, care about open-source licensing, or want features like sequences and thread pooling that MySQL locks behind Enterprise. MariaDB gives you more database for fewer constraints. If that is where you land, managed MariaDB on Layerbase runs the same server you just tested locally.

Both are production-grade with active communities and long track records. You won't regret either choice for a standard web application. The differences matter at the margins. This post should help you figure out which margins matter to you.

FAQ

Is MariaDB the same as MySQL?

Not any more, though they started that way. MariaDB forked from MySQL in 2009 after Oracle acquired Sun Microsystems, and sixteen years of separate development has put real distance between them. They still share the wire protocol and most of the SQL syntax, which is why your ORM and your admin tools treat them interchangeably, but the feature sets, the optimizers, and the licensing have all diverged.

Can I switch from MySQL to MariaDB without changing my code?

For a normal application, usually yes. The shared wire protocol means Prisma, Drizzle, TypeORM, Sequelize, Knex, and TablePlus all connect to either one, and the SQL you wrote for a CRUD app is very likely portable. Where it gets interesting is if you leaned on something version-specific: multi-valued JSON indexes going to MariaDB, or system-versioned tables and sequences coming back the other way. Dump, restore into the other engine, and run your test suite; that is the honest way to find out. What actually breaks on the way into MariaDB is the error-by-error version of that dump, and the free way to try it.

Which is faster, MySQL or MariaDB?

Close enough that it should not decide this for you. Both use InnoDB, so the baseline is the same engine. The optimizers have diverged and produce different execution plans, which means MariaDB wins some complex JOIN benchmarks and MySQL wins some simple-lookup ones. The one structural difference worth knowing is that MariaDB ships thread pooling in the community edition while MySQL keeps it behind Enterprise, which shows up under high concurrency.

Is MariaDB really free?

Yes, GPL v2 with no proprietary edition held above it. MariaDB plc sells commercial support and the MaxScale proxy, but the database itself is fully open, and features like thread pooling that MySQL gates behind Enterprise ship in the community build. MySQL is dual-licensed: the community edition is GPL v2 and genuinely functional, while Thread Pool, audit logging, and MySQL Router for high availability are paid.

Does MariaDB support JSON_TABLE?

Yes, since MariaDB 10.6. This is a common misconception and one this post got wrong until August 2026. What MariaDB still does not have is multi-valued indexes on JSON arrays, so if you need to index individual array elements rather than just query them, that remains a MySQL-only capability.

Is there a managed MariaDB cloud service?

Several. AWS RDS has offered MariaDB for years, and MariaDB plc reacquired SkySQL in August 2025 and relaunched it as MariaDB Cloud, so there is a self-serve first-party option again. Layerbase Cloud runs managed MariaDB too, next to MySQL, so you can put both on the same account and compare them against your own schema.

Which should I pick for a new project in 2026?

MySQL if your hosting or framework already assumes it, if you are deep in the WordPress or Laravel ecosystem, or if you need multi-valued JSON indexes. MariaDB if you want system-versioned tables for audit history, sequences instead of AUTO_INCREMENT workarounds, thread pooling without an Enterprise bill, or a license with nothing proprietary above it. With no specific requirement pulling either way, MySQL is the safer default purely on ecosystem breadth.

Wrapping Up

Close your connections:

typescript
await my.end()
await maria.end()

To manage your local instances with the Layerbase CLI:

bash
lbase stop mysql1    # Stop MySQL
lbase stop maria1    # Stop MariaDB
lbase start mysql1   # Start MySQL again
lbase start maria1   # Start MariaDB again
lbase list           # See all your database instances

The Layerbase CLI supports 20+ database engines, so you can run MySQL and MariaDB side by side without juggling separate installs. Layerbase Desktop is available if you prefer a GUI on macOS.