TechEarl

MySQL MyISAM to InnoDB Conversion: Why and How

MyISAM has no transactions, no foreign keys, and corrupts on crash. InnoDB has been the MySQL default since 5.5 and is the only engine that gets new features. Here is how to convert every MyISAM table in a MySQL database with a single SQL script.

Ishan Karunaratne⏱️ 13 min readUpdated
Share thisCopied
Convert every MyISAM table to InnoDB: why MyISAM is dead (no transactions, no FKs, crash-unsafe), the ALTER TABLE syntax, FULLTEXT considerations, and a script to convert all tables in a database.

MyISAM was the default MySQL storage engine until 5.5 (released 2010). It is still around because some hosting providers don't bother changing the default, and because old CREATE TABLE statements without an ENGINE= clause used to land on MyISAM. The case for converting is unambiguous: MyISAM has no transactions, no foreign-key enforcement, no crash-safety (a hard restart can leave a table in a state that needs REPAIR TABLE), and is excluded from every major MySQL feature added since 5.5. InnoDB is the only engine the MySQL team is actively developing, the only one that supports INSTANT schema changes, and the only one that survives a kernel panic without manual intervention. Below is the conversion: one query to find every MyISAM table you own, the ALTER TABLE to convert each one, what to do with FULLTEXT indexes (the historical reason to stay on MyISAM), and the size and time tradeoffs.

How do I convert a MyISAM table to InnoDB?

The conversion is a single SQL statement per table: ALTER TABLE table_name ENGINE=InnoDB;. MySQL reads the table data, writes it back in InnoDB format with the same columns and indexes, then drops the old MyISAM files. To find every MyISAM table in a database, query information_schema.tables filtered by engine='MyISAM'. To convert all of them at once, generate ALTER TABLE statements from that query and execute the result. Before converting, check three things: FULLTEXT indexes were MyISAM-only until MySQL 5.6, so verify your version supports InnoDB FULLTEXT before the conversion (5.6+ is fine). Foreign keys were ignored on MyISAM, so any REFERENCES clauses in the schema will now be enforced and may reveal orphan rows. Disk space doubles temporarily during conversion. The conversion is online (the table is readable during ALTER) on MySQL 5.6+ with InnoDB.

Jump to:

Why convert at all

The honest list. MyISAM has been the wrong choice for production workloads since 2010, and the gap has only widened.

PropertyMyISAMInnoDB
ACID transactionsNoYes
Foreign key enforcementIgnored (the syntax parses but does nothing)Enforced
Crash safetyNo — REPAIR TABLE needed after hard shutdownYes — automatic recovery from redo log
Row-level lockingNo (table-level only)Yes
Online schema changes (ALGORITHM=INSTANT/INPLACE)No — every ALTER rebuildsYes
Replication compatibilityStatement-only safelyRow-based replication works
Default since(was default through 5.1)MySQL 5.5+
Active developmentNone since around 2010Continuously developed
FULLTEXTYes (the historical advantage)Yes since MySQL 5.6
Spatial indexes on InnoDBn/aYes since 5.7.5

The historical reasons to choose MyISAM (faster COUNT(*), smaller on-disk size, FULLTEXT) no longer outweigh the costs. MySQL 8.0.14 parallelizes the InnoDB clustered-index scan for unfiltered COUNT(*), which makes it much faster (it is still a scan, not an O(1) cached count). FULLTEXT works on both. The disk-size delta on a typical schema is maybe 10-15% and is more than offset by InnoDB's compressed row formats.

If you have a WordPress site running on MyISAM (common on shared hosts that haven't updated defaults in a decade), the conversion has measurable effects on performance under concurrent writes, on the ability to use modern caching plugins that rely on transactions, and on backup reliability.

Find every MyISAM table

The diagnostic query. Run this first, before any conversion:

sql
SELECT table_schema, table_name, engine, table_rows,
       ROUND(data_length / 1024 / 1024, 2)  AS data_mb,
       ROUND(index_length / 1024 / 1024, 2) AS index_mb
FROM information_schema.tables
WHERE engine = 'MyISAM'
  AND table_schema NOT IN ('mysql', 'sys', 'information_schema', 'performance_schema')
ORDER BY (data_length + index_length) DESC;

This lists every user MyISAM table with size estimates. Pin the output; you'll use it to track progress through the conversion and to compare sizes before/after.

The conversion query

For a single table:

sql
ALTER TABLE my_table ENGINE=InnoDB;

That's it. MySQL handles the rest: locks the table for the duration (with row-level reads still permitted on 5.6+ with ALGORITHM=INPLACE), copies rows into a new InnoDB table, swaps the file pointers, drops the MyISAM table.

Time-wise, a 1 GB MyISAM table converts in roughly 30-90 seconds on a modern SSD-backed server. A 50 GB table is 30-60 minutes. The duration scales with the total table size (data + indexes), not row count.

To explicitly request the online algorithm (5.6+):

sql
ALTER TABLE my_table ENGINE=InnoDB, ALGORITHM=INPLACE, LOCK=NONE;

If that fails (some older 5.6 builds, or schemas with FULLTEXT being added concurrently), fall back to the default ALGORITHM=COPY which takes a metadata lock for the whole operation.

Bulk convert every table in a database

The trick: have MySQL generate the SQL for you, then execute the generated SQL. Two-step procedure.

Step 1: generate the ALTER statements:

sql
SELECT CONCAT('ALTER TABLE `', table_schema, '`.`', table_name, '` ENGINE=InnoDB;') AS sql_stmt
FROM information_schema.tables
WHERE engine = 'MyISAM'
  AND table_schema NOT IN ('mysql', 'sys', 'information_schema', 'performance_schema')
ORDER BY (data_length + index_length) ASC;

The ORDER BY ASC (smallest first) is intentional: short ALTERs build confidence; if a small table fails, you know early rather than mid-way through a 50 GB conversion.

Step 2: capture and execute. From the command line, redirect the query to a file and run it back:

bash
# 1. Generate the script
mysql -u root -p -N -e "
    SELECT CONCAT('ALTER TABLE \`', table_schema, '\`.\`', table_name, '\` ENGINE=InnoDB;')
    FROM information_schema.tables
    WHERE engine = 'MyISAM'
      AND table_schema NOT IN ('mysql', 'sys', 'information_schema', 'performance_schema')
    ORDER BY (data_length + index_length) ASC;
" > convert-to-innodb.sql

# 2. Inspect the file — sanity check the count and the table list
wc -l convert-to-innodb.sql
head convert-to-innodb.sql

# 3. Execute it (logs each ALTER and how long it took)
mysql -u root -p --verbose < convert-to-innodb.sql 2>&1 | tee convert-to-innodb.log

The --verbose flag makes mysql print each statement before executing, so the log file gives you a per-table breakdown of time.

For a single specific database, add AND table_schema = 'mydb' to the WHERE clause.

Handling FULLTEXT indexes

FULLTEXT indexes were MyISAM-only through MySQL 5.5. InnoDB gained FULLTEXT support in 5.6. If your MySQL version is 5.6 or higher, MyISAM FULLTEXT indexes are converted directly to InnoDB FULLTEXT during ALTER TABLE ... ENGINE=InnoDB. No special steps needed.

If you somehow still run MySQL 5.5 (which has been EOL since 2018), you must drop the FULLTEXT index before conversion, convert the engine, then choose whether to:

  • Recreate the FULLTEXT on InnoDB after upgrading to 5.6+,
  • Move FULLTEXT search out of MySQL entirely (Elasticsearch, MeiliSearch, Postgres tsvector, or ElasticPress for WordPress),
  • Use MySQL 8.0's improved FULLTEXT (still on InnoDB) which has better tokenization.

For WordPress sites that rely on FULLTEXT for the search box, InnoDB FULLTEXT performs adequately on databases up to a few million posts. Beyond that, an external search engine is usually the right answer.

Foreign keys are now enforced

MyISAM ignored FOREIGN KEY clauses in CREATE TABLE. The syntax parsed and was stored, but no constraint was actually enforced. After conversion to InnoDB, these constraints become real. Two failure modes:

Orphan rows. If a child table has rows whose foreign-key value doesn't match any row in the parent, the constraint is violated at the moment InnoDB enforces it. Symptoms: the ALTER TABLE for the child fails with a Cannot add foreign key constraint error.

Find orphans before converting:

sql
-- Example: posts.user_id should reference users.id
SELECT p.user_id
FROM posts p
LEFT JOIN users u ON u.id = p.user_id
WHERE u.id IS NULL
  AND p.user_id IS NOT NULL;

Fix orphans (delete, or re-point to a placeholder user) before the conversion runs.

Circular dependencies. If two tables reference each other, you can't ALTER either one first without breaking the reference. Convert both with foreign keys temporarily disabled:

sql
SET FOREIGN_KEY_CHECKS = 0;
ALTER TABLE parent ENGINE=InnoDB;
ALTER TABLE child  ENGINE=InnoDB;
SET FOREIGN_KEY_CHECKS = 1;

The constraints are re-evaluated when FOREIGN_KEY_CHECKS=1 is re-enabled. Orphan-row violations will still surface at that point.

Performance differences

The numbers vary by workload. General guidance from production conversions:

WorkloadMyISAMInnoDBNotes
Read-heavy, low concurrencySlightly fasterAbout the sameThe MyISAM advantage is mostly gone since 5.6
Read-heavy, high concurrencyBottlenecked on table-level lockMuch faster (row-level)The big win
Write-heavyBottlenecked on table-level lockMuch fasterThe big win
Mixed transactionalNo transactions, so data corruption likelyWorks correctlyInnoDB is the only correct choice
COUNT(*) without WHEREInstant (cached count)Parallel scan on 8.0.14+, serial scan on older8.0.14 parallelizes the scan, but it is still a scan
Bulk INSERTFaster (no transaction overhead)Slower without START TRANSACTION batchingWrap in transactions
Crash recoveryREPAIR TABLE required, can lose dataAutomatic from redo logNo contest

For a real-world WordPress site moving from MyISAM to InnoDB, expect concurrent-write throughput to improve by 3-10x. Read latency under load improves a similar amount. Crash recovery becomes routine instead of an emergency.

Disk space during conversion

ALTER TABLE ... ENGINE=InnoDB is implemented as: create a new InnoDB table, copy rows, drop the old MyISAM. During the copy, both the source MyISAM and the destination InnoDB exist on disk simultaneously. Free-disk requirement is roughly:

code
free space >= size of the largest MyISAM table being converted + 10% margin

For the bulk-convert script, you don't need 2x of the total — only 2x of the largest single table, since tables are converted one at a time. But check this before starting:

sql
-- Find the largest MyISAM table
SELECT table_schema, table_name,
       ROUND((data_length + index_length) / 1024 / 1024 / 1024, 2) AS total_gb
FROM information_schema.tables
WHERE engine = 'MyISAM'
ORDER BY total_gb DESC
LIMIT 5;

If the largest table is 100 GB, you need at least 110 GB free.

Post-conversion, InnoDB tables are typically 10-30% larger than the MyISAM equivalent on disk. The two-byte primary-key overhead per row and the clustered-index structure account for most of the difference. The cost is real but small.

Verification

After the bulk conversion:

sql
-- Should return zero rows
SELECT table_schema, table_name, engine
FROM information_schema.tables
WHERE engine = 'MyISAM'
  AND table_schema NOT IN ('mysql', 'sys', 'information_schema', 'performance_schema');

-- Engine distribution
SELECT engine, COUNT(*) FROM information_schema.tables
WHERE table_schema NOT IN ('mysql','sys','information_schema','performance_schema')
GROUP BY engine;

-- Should not have any obvious row-count discrepancies
-- (run before and after — InnoDB row counts in information_schema are estimates,
--  so for exact counts query the table directly)
SELECT COUNT(*) FROM critical_table;

The mysql.user, mysql.db, and the rest of the mysql system schema tables are intentionally still MyISAM through 5.7 and become InnoDB in 8.0. Don't convert those manually; they're managed by the upgrade process.

What to do next

After the conversion:

External references:

FAQ

Practically none. Even the historical edge cases (read-only data warehouses, FULLTEXT-only tables) are now better served by InnoDB or by purpose-built engines. MyISAM is in the codebase for legacy compatibility, not as a viable choice.

The one case I'd consider it: an embedded analytic table loaded once at boot, read-only, and never crashed. And even then, InnoDB read performance is competitive enough that the savings are negligible.

No. The ALTER TABLE preserves all rows, all columns, all indexes (including FULLTEXT on 5.6+), all default values, and all charset/collation settings. The only "loss" is the foreign-key constraint enforcement gain: orphan rows that existed silently in MyISAM may now fail to insert as new rows reference dropped parents.

The right pre-flight is to detect orphan rows ahead of the conversion and decide whether to delete them or update them, rather than discover them during the ALTER.

Roughly 1-2 minutes per gigabyte of table size on SSD-backed hardware with reasonable memory. A 100 GB database with twenty tables typically converts in 90-180 minutes total wall-clock time, run sequentially.

You can run conversions in parallel against different tables to compress the wall-clock time, but watch for contention if the tables share parent-child foreign keys.

On 5.5 and earlier, the table is locked for the entire ALTER (no reads, no writes). On 5.6+ with InnoDB, the conversion uses an online algorithm (ALGORITHM=INPLACE, LOCK=NONE) so reads and writes continue throughout — there's just a brief metadata lock at the very start and end.

For very large tables on busy production servers, schedule the conversion during low-traffic windows anyway. The online algorithm is fast but still touches every row.

Convert. WordPress has worked on InnoDB since version 3.5 (2012). The dbDelta() schema generator creates InnoDB tables by default for new installs since then. The benefits for WordPress are large: concurrent writes from form submissions, comments, and AJAX endpoints all scale better; backup plugins work more reliably; caching plugins that use transactional logic actually work.

For an old WordPress install on MyISAM, the bulk-convert script in this article is the one-time fix. Pair it with the password hash modernization if the install also predates bcrypt.

Yes. mysqldump --all-databases --single-transaction won't work on MyISAM tables because they don't support transactions. Use --lock-all-tables or accept the slight inconsistency and just snapshot. For a one-time conversion on a quiet maintenance window, the simplest answer is to stop the application, dump everything, then convert.

For a critical production database, use Percona XtraBackup or LVM snapshot before touching anything.

Sometimes. InnoDB's clustered index means primary-key lookups are very fast and PK-ordered range scans are sequential. MyISAM's heap-style storage gave different cost estimates. The optimizer rewrites plans for InnoDB tables after conversion; some queries get faster, some get slower until you re-tune indexes.

Run ANALYZE TABLE after conversion to refresh statistics, then re-check the EXPLAIN output of your top 10 queries. Adjust indexes as needed.

Yes. A single ALTER TABLE my_table ENGINE=InnoDB; converts that one table only. Mixing engines in the same database is fine — that's the typical state during an incremental migration.

If the table has foreign-key relationships to MyISAM siblings, those references will start being enforced once the converted table sees them as parents. Either convert the full graph at once, or wrap the per-table conversions with SET FOREIGN_KEY_CHECKS = 0/1.

Sources

Authoritative references this article was fact-checked against.

TagsMySQLInnoDBMyISAMStorage EngineMigrationDatabase Administration

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Software Systems Architect · Senior Software Engineer · Engineering Leadership

Software systems architect and senior software engineer with more than two decades designing, building, and running production software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Now a CTO, though what I write here is drawn from the full arc of that work, across architecture, engineering, and operations, not any single job.

Keep reading

Related posts

nvm vs fnm vs Volta comparison: which Node.js version manager to choose by speed, auto-switching, and per-project pinning

nvm vs fnm vs Volta: Which Node Version Manager?

nvm vs fnm vs Volta, compared by speed, auto-switching, platform support, and pinning model. Which Node version manager to pick in 2026, with install commands, the .nvmrc vs package.json question, and honest caveats from running all three.