MySQL Storage Engines
A storage engine (also called a table handler) is the component of MySQL responsible for storing, retrieving, and managing data on disk. MySQL's pluggable architecture lets different tables use different engines in the same database. Each engine makes different trade-offs between transactions, locking, memory use, and feature support.
Listing Available Engines
-- Show all storage engines supported by this MySQL installation SHOW ENGINESG -- Output fields: -- Engine -- engine name -- Support -- DEFAULT, YES, NO, or DISABLED -- Comment -- description -- Transactions -- YES or NO -- XA -- distributed transaction support -- Savepoints -- YES or NO
InnoDB — The Default Engine
InnoDB has been MySQL's default storage engine since version 5.5. It is the right choice for virtually all modern applications.
Feature | InnoDB Support |
|---|---|
ACID transactions | Full support — COMMIT, ROLLBACK, SAVEPOINT |
Foreign keys | Yes — enforced with cascades |
Locking | Row-level — best concurrency for OLTP |
MVCC | Yes — readers never block writers |
Crash recovery | Automatic via redo log + doublewrite buffer |
Full-text search | Yes (MySQL 5.6+) |
Spatial data | Yes |
Online DDL | Extensive support (ALTER TABLE without full table lock) |
-- Create a table explicitly using InnoDB (default, so optional) CREATE TABLE orders ( order_id INT AUTO_INCREMENT PRIMARY KEY, customer_id INT NOT NULL, order_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, total DECIMAL(10,2), FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ON DELETE CASCADE ) ENGINE = InnoDB; -- Check which engine a table uses SELECT TABLE_NAME, ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE(); -- Show InnoDB status SHOW ENGINE INNODB STATUSG
InnoDB Buffer Pool
The buffer pool is InnoDB's most important performance setting. It is an in-memory cache for table data and indexes. A larger buffer pool means more data is served from RAM rather than disk.
-- Check current buffer pool size
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
-- Check buffer pool hit ratio (aim for > 99%)
SELECT
(1 - (
variable_value / (
SELECT variable_value
FROM performance_schema.global_status
WHERE variable_name = 'Innodb_buffer_pool_reads'
) * 100
)) AS hit_ratio_pct
FROM performance_schema.global_status
WHERE variable_name = 'Innodb_buffer_pool_read_requests';
-- Set buffer pool to 70-80% of available RAM in my.cnf
-- [mysqld]
-- innodb_buffer_pool_size = 4Ginnodb_buffer_pool_size to 70-80% of total RAM. This is the single biggest performance tuning lever for InnoDB.MyISAM — The Legacy Engine
MyISAM was MySQL's default engine before InnoDB took over. It is simpler but lacks the features modern applications need:
Feature | MyISAM |
|---|---|
Transactions | No — no COMMIT or ROLLBACK |
Foreign keys | No — FK syntax accepted but not enforced |
Locking | Table-level — one writer blocks all readers and writers |
Crash recovery | Manual repair needed (myisamchk) |
Full-text search | Yes (traditional; predates InnoDB full-text) |
Storage | .MYD (data) + .MYI (index) + .frm (format) files per table |
-- MyISAM table (rare in modern MySQL) CREATE TABLE search_log ( id INT AUTO_INCREMENT PRIMARY KEY, query VARCHAR(255), logged DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE = MyISAM; -- MyISAM has a COUNT(*) shortcut (no full scan needed for simple COUNT) SELECT COUNT(*) FROM search_log; -- Very fast on MyISAM, reads table metadata
MEMORY Engine
The MEMORY engine stores all data in RAM. It is extremely fast for reads and writes but all data is lost when MySQL restarts.
Feature | MEMORY |
|---|---|
Transactions | No |
Persistence | Data lost on restart — table structure preserved |
Locking | Table-level |
Index types | HASH (default, fast for =) and BTREE (for range queries) |
NULL values | No support for NULL in index columns |
Variable-length columns | Stored as fixed-length — VARCHAR treated as CHAR |
-- Good use case: session cache, temporary computation table CREATE TABLE rate_limiter ( client_ip VARCHAR(45) NOT NULL, request_count INT NOT NULL DEFAULT 0, window_start DATETIME NOT NULL, PRIMARY KEY (client_ip) ) ENGINE = MEMORY; -- Hash index (default): perfect for exact equality lookups -- ALTER TABLE rate_limiter ADD INDEX USING HASH (client_ip); -- BTree index: needed for range queries CREATE TABLE temp_sort_work ( id INT, score DECIMAL(10,4), INDEX USING BTREE (score) ) ENGINE = MEMORY;
tmp_table_size or max_heap_table_size, they are converted to on-disk InnoDB temp tables automatically.ARCHIVE Engine
The ARCHIVE engine stores rows in compressed format (zlib). It is optimized for write-once, read-rarely workloads — ideal for audit logs, access logs, or historical records that must be retained but rarely queried.
Feature | ARCHIVE |
|---|---|
Compression | Yes — roughly 10:1 compression ratio typical |
INSERT | Supported |
UPDATE/DELETE | Not supported |
Indexes | Only AUTO_INCREMENT primary key |
Transactions | No |
SELECT | Full table scan only — no indexes on other columns |
CREATE TABLE access_log_archive ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id INT, endpoint VARCHAR(255), ip_address VARCHAR(45), logged_at DATETIME ) ENGINE = ARCHIVE;
BLACKHOLE Engine
The BLACKHOLE engine accepts writes but discards them silently. Reads always return empty. It is used in replication topologies as a relay server — the relay receives writes and forwards them via the binary log to downstream replicas, without actually storing any data itself.
-- Used in replication relay topology
CREATE TABLE relay_events (
id INT AUTO_INCREMENT PRIMARY KEY,
payload TEXT
) ENGINE = BLACKHOLE;
-- INSERT is accepted (logged to binlog), but SELECT returns nothing
INSERT INTO relay_events (payload) VALUES ('forward to replica');
SELECT * FROM relay_events; -- Returns empty result setCSV Engine
The CSV engine stores table data as a comma-separated values file. The .CSV file can be opened directly in Excel or any text editor — useful for simple data exchange.
CREATE TABLE export_data ( id INT NOT NULL, -- CSV engine: all columns must be NOT NULL name VARCHAR(100) NOT NULL, value DECIMAL(10,2) NOT NULL ) ENGINE = CSV; -- The data file is stored at: datadir/db_name/export_data.CSV -- Open it in any spreadsheet application
Switching Storage Engines
-- Convert a MyISAM table to InnoDB (online, non-blocking in MySQL 5.6+) ALTER TABLE old_table ENGINE = InnoDB; -- Convert InnoDB to ARCHIVE for cold data ALTER TABLE completed_orders_2020 ENGINE = ARCHIVE; -- Convert MEMORY table to InnoDB ALTER TABLE temp_results ENGINE = InnoDB; -- Check table engine before and after SELECT TABLE_NAME, ENGINE, DATA_LENGTH, INDEX_LENGTH FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'mydb' ORDER BY ENGINE, TABLE_NAME;
ALTER TABLE ... ENGINE = InnoDB on a large MyISAM table creates a full copy of the table — it will temporarily double disk space usage. Plan for this when migrating large tables.Storage Engine Comparison Summary
Engine | Transactions | Foreign Keys | Locking | Use Case |
|---|---|---|---|---|
InnoDB | Yes | Yes | Row-level | Default for all OLTP applications |
MyISAM | No | No | Table-level | Legacy only — avoid for new development |
MEMORY | No | No | Table-level | Temporary data, caches, rate limiting |
ARCHIVE | No | No | Row-level (insert only) | Compressed write-once logs/audits |
BLACKHOLE | No | No | None | Replication relay, testing |
CSV | No | No | Table-level | Data exchange via flat files |
InnoDB File-per-Table
By default, InnoDB stores each table's data in its own .ibd file (file-per-table mode). This is recommended over the legacy shared tablespace:
-- Verify file-per-table is enabled (default ON in MySQL 5.6+) SHOW VARIABLES LIKE 'innodb_file_per_table'; -- Each table has its own file: -- datadir/mydb/orders.ibd -- datadir/mydb/customers.ibd -- Check tablespace file for a specific table SELECT TABLESPACE_NAME, FILE_NAME, FILE_SIZE / 1024 / 1024 AS size_mb FROM information_schema.FILES WHERE FILE_NAME LIKE '%mydb%' ORDER BY FILE_SIZE DESC; -- Reclaim space after large DELETEs (reorganizes and truncates the ibd file) OPTIMIZE TABLE orders; -- equivalent to ALTER TABLE orders ENGINE=InnoDB
Checking Table Engine and Size
-- Find all non-InnoDB tables (run this to find legacy tables)
SELECT
TABLE_SCHEMA,
TABLE_NAME,
ENGINE,
TABLE_ROWS,
ROUND((DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024, 2) AS size_mb
FROM information_schema.TABLES
WHERE TABLE_SCHEMA NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys')
AND ENGINE != 'InnoDB'
ORDER BY size_mb DESC;
-- List all tables with their engine and size
SELECT
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 TABLE_SCHEMA = DATABASE()
ORDER BY (DATA_LENGTH + INDEX_LENGTH) DESC;Best Practices
Use InnoDB for all tables unless you have a very specific reason not to
Migrate any remaining MyISAM tables to InnoDB — run ALTER TABLE t ENGINE=InnoDB
Set innodb_buffer_pool_size to 70-80% of RAM on dedicated database servers
Use MEMORY tables only for truly ephemeral data — plan for the data to vanish on any server restart
ARCHIVE is a good choice for append-only audit logs that must be kept years but rarely queried
Verify all your tables are InnoDB: SELECT TABLE_NAME, ENGINE FROM information_schema.TABLES WHERE ENGINE != 'InnoDB' AND TABLE_SCHEMA = DATABASE()
Run OPTIMIZE TABLE periodically on tables that receive many DELETEs to reclaim disk space