Himanshu SharmaSoftware engineer
Let’s talk

MySQL · Lesson 40 of 50

Storage engines and character sets

Understand MySQL storage engines (InnoDB, MyISAM, MEMORY) and how character sets and collations control text storage, comparison and sorting.

15 minFaster and saferUpdated 22 October 2026

Awaiting technical review. This lesson is readable but not yet listed in search engines.

By the end of this lessonChoose a storage engine, explain what InnoDB gives you, and pick the right character set and collation for text columns.

Two settings that shape every table

When you create a table, MySQL attaches two settings you may never have noticed: the storage engine (how data is physically stored) and the character set and collation (how text is encoded, compared and sorted). The defaults are good, but understanding them prevents some nasty surprises.

Storage engines

The part of MySQL that reads and writes data on disk is pluggable. Different engines make different trade-offs. See which are available and what they support:

sql
SELECT ENGINE, SUPPORT, TRANSACTIONS, XA, SAVEPOINTS
FROM information_schema.ENGINES
WHERE ENGINE IN ('InnoDB', 'MyISAM', 'MEMORY')
ORDER BY ENGINE;
result
+--------+---------+--------------+------+------------+
| ENGINE | SUPPORT | TRANSACTIONS | XA   | SAVEPOINTS |
+--------+---------+--------------+------+------------+
| InnoDB | DEFAULT | YES          | YES  | YES        |
| MEMORY | YES     | NO           | NO   | NO         |
| MyISAM | YES     | NO           | NO   | NO         |
+--------+---------+--------------+------+------------+

Check which engine your tables use:

sql
SELECT TABLE_NAME, ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'shop' ORDER BY TABLE_NAME;
result
+-------------+--------+
| TABLE_NAME  | ENGINE |
+-------------+--------+
| categories  | InnoDB |
| customers   | InnoDB |
| departments | InnoDB |
| employees   | InnoDB |
| order_items | InnoDB |
| orders      | InnoDB |
| products    | InnoDB |
+-------------+--------+
Engine Transactions Foreign keys Locking Typical use
InnoDB yes yes row level everything: the default since MySQL 5.5
MyISAM no no whole table legacy tables, now rarely a good choice
MEMORY no no whole table temporary, rebuildable lookup data
CSV, ARCHIVE no no table special cases

What InnoDB gives you

  • Transactions with commit and rollback (see the previous lesson).
  • Foreign keys to protect relationships.
  • Row-level locking, so many sessions can write to different rows of one table at once.
  • Crash recovery: after a power cut, committed data is restored from the redo log.
  • MVCC (multi-version concurrency control), so readers do not block writers.

You can choose an engine per table with ENGINE=..., but unless you have an unusual reason, leave it as InnoDB.

Seeing the difference

A rollback shows it clearly. Make one table of each kind and try to undo an insert:

sql
CREATE DATABASE IF NOT EXISTS practice;
USE practice;
DROP TABLE IF EXISTS t_innodb, t_myisam;
CREATE TABLE t_innodb (id INT PRIMARY KEY, note VARCHAR(20)) ENGINE=InnoDB;
CREATE TABLE t_myisam (id INT PRIMARY KEY, note VARCHAR(20)) ENGINE=MyISAM;

START TRANSACTION;
INSERT INTO t_innodb VALUES (1, 'innodb row');
INSERT INTO t_myisam VALUES (1, 'myisam row');
ROLLBACK;

SELECT 'innodb' AS engine, COUNT(*) AS rows_left FROM t_innodb
UNION ALL
SELECT 'myisam', COUNT(*) FROM t_myisam;
result
+--------+-----------+
| engine | rows_left |
+--------+-----------+
| innodb |         0 |
| myisam |         1 |
+--------+-----------+

The InnoDB insert vanished with the rollback. The MyISAM row is still there, because that engine ignores transactions. If the server had crashed halfway through a multi-statement update on MyISAM, you could have been left with half-finished data.

To change an existing table: ALTER TABLE t_myisam ENGINE = InnoDB;. It rebuilds the table, so on a big table plan the time.

Character sets

A character set is the list of characters a column may hold and how each is encoded in bytes. Old defaults caused years of pain with accented letters and emoji. The modern answer:

  • utf8mb4: real UTF-8 that stores up to 4 bytes per character. It covers every language and emoji. MySQL 8 uses it by default.
  • utf8 (really utf8mb3): an older, 3-byte subset that cannot store emoji or some rare characters. Avoid it.
  • latin1: Western European only.
sql
SELECT @@character_set_server AS server_charset, @@collation_server AS server_collation;
result
+----------------+--------------------+
| server_charset | server_collation   |
+----------------+--------------------+
| utf8mb4        | utf8mb4_0900_ai_ci |
+----------------+--------------------+

Compare the characters and the bytes of an emoji:

sql
SELECT CHAR_LENGTH('😀') AS characters, LENGTH('😀') AS bytes;
result
+------------+-------+
| characters | bytes |
+------------+-------+
|          1 |     4 |
+------------+-------+

The older three-byte set cannot hold it. Inserting into a utf8mb3 column fails in strict mode:

sql
DROP TABLE IF EXISTS t_old, t_new;
CREATE TABLE t_old (txt VARCHAR(10)) CHARACTER SET utf8mb3;
CREATE TABLE t_new (txt VARCHAR(10)) CHARACTER SET utf8mb4;
INSERT INTO t_new VALUES ('hello 😀');
SELECT txt FROM t_new;
result
+------------+
| txt        |
+------------+
| hello 😀     |
+------------+
sql
INSERT INTO t_old VALUES ('hello 😀');
result
ERROR 1366 (HY000): Incorrect string value: '\xF0\x9F\x98\x80' for column 'txt' at row 1

Remember that the connection also has a character set. Clients should connect with utf8mb4 (most modern drivers do; PHP PDO needs charset=utf8mb4 in the DSN), otherwise text can be corrupted on the way in even if the column is fine.

Collations

A collation is a set of rules for comparing and sorting text in a character set. The name tells you the rules:

Part Meaning
utf8mb4_0900_ai_ci MySQL 8 default. Unicode 9.0 rules, accent-insensitive, case-insensitive
utf8mb4_0900_as_cs accent-sensitive, case-sensitive
utf8mb4_bin compares raw byte values: case and accent sensitive, and strictly binary
utf8mb4_unicode_ci older Unicode rules, common on tables created in MySQL 5.7
sql
SELECT 'a' = 'A' AS same_case_ai_ci,
       'cafe' = 'café' AS same_accent_ai_ci,
       ('a' COLLATE utf8mb4_0900_as_cs) = 'A' AS same_case_cs,
       ('cafe' COLLATE utf8mb4_0900_as_cs) = 'café' AS same_accent_as;
result
+-----------------+-------------------+--------------+----------------+
| same_case_ai_ci | same_accent_ai_ci | same_case_cs | same_accent_as |
+-----------------+-------------------+--------------+----------------+
|               1 |                 1 |            0 |              0 |
+-----------------+-------------------+--------------+----------------+

With the default collation, 'a' equals 'A' and 'cafe' equals 'café'. That is why a search for asha finds Asha. If you need exact matching, for example for case-sensitive codes, use a _bin or _cs collation on that column, or override it in a query:

sql
SELECT first_name FROM shop.customers WHERE first_name = 'asha' COLLATE utf8mb4_0900_as_cs;
SELECT first_name FROM shop.customers WHERE first_name = 'asha';
result
+------------+
| first_name |
+------------+
| Asha       |
+------------+

The first query finds nobody, because asha and Asha differ under a case-sensitive collation. The second finds Asha. Collations also decide sorting:

sql
SELECT x FROM (SELECT 'b' AS x UNION ALL SELECT 'A' UNION ALL SELECT 'a' UNION ALL SELECT 'B') AS letters ORDER BY x COLLATE utf8mb4_bin;
result
+---+
| x |
+---+
| A |
| B |
| a |
| b |
+---+

With the binary collation all capitals sort before lowercase letters, which is what the byte values say. The default collation treats a and A as equal and orders them next to each other.

Setting them on databases, tables and columns

The settings cascade: server, then database, then table, then column. Set them explicitly when you create things:

sql
DROP TABLE IF EXISTS messages;
CREATE TABLE messages (
  id   INT PRIMARY KEY AUTO_INCREMENT,
  body TEXT
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;

To convert an existing table, for example from latin1:

sql
ALTER TABLE old_table CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;

Conversion rewrites the table, and a column that is a foreign key must match its partner exactly, including the collation. Mismatched collations are also the reason joins on text columns can give "Illegal mix of collations" errors.

Common mistakes

  • Using utf8 (utf8mb3) and losing emoji or other characters.
  • Connecting with the wrong client character set, which garbles text.
  • Mixing collations between tables and then failing to join or compare them.
  • Using MyISAM for new tables.
  • Expecting case-sensitive matching under the default collation.

Exercise

Create a table codes_demo with a column code VARCHAR(10) using a case-sensitive collation (utf8mb4_0900_as_cs), insert 'ab12' and 'AB12', and show that a search for 'ab12' finds only one of them. Then show that the same data in the default collation finds both.

Show one possible answer
sql
DROP TABLE IF EXISTS codes_demo;
CREATE TABLE codes_demo (code VARCHAR(10) COLLATE utf8mb4_0900_as_cs);
INSERT INTO codes_demo VALUES ('ab12'), ('AB12');
SELECT code FROM codes_demo WHERE code = 'ab12';
SELECT code FROM codes_demo WHERE code = 'ab12' COLLATE utf8mb4_0900_ai_ci;
result
+------+
| code |
+------+
| ab12 |
+------+
+------+
| code |
+------+
| ab12 |
| AB12 |
+------+

The first query returns only ab12. The second overrides the collation and returns both rows.

Key points

  • A storage engine decides how a table's data is stored and what features it has. InnoDB is the default and the right choice almost always: transactions, foreign keys, crash recovery and row-level locking.
  • MyISAM has no transactions or foreign keys, and MEMORY loses its data when the server restarts.
  • A character set (use utf8mb4) says which characters can be stored; a collation says how they compare and sort.
  • In collation names, ai/as mean accent-insensitive/sensitive and ci/cs mean case-insensitive/sensitive; _bin compares raw bytes.

Check yourself

1. Which storage engine supports transactions and foreign keys?
2. What does the utf8 character set (an alias of utf8mb3) fail to store?
3. What does a collation ending in _ci mean?
4. Rows in a MEMORY table after the server restarts?