LearnThatStack Ace your next interview

MySQL

Commands for MySQL 8.4 LTS and 8.0 Checked Oct 2026 81 commands

The model

  • InnoDB stores each table in a clustered index, a B-tree in primary key order. Secondary indexes hold that key.
  • COMMIT waits only for the redo log to reach disk. Changed pages wait in the buffer pool and reach disk later.
  • A plain SELECT reads a snapshot, rebuilt from the undo log, and takes no locks. That's multi-version concurrency control (MVCC).
  • Writes and locking reads lock index records, and under REPEATABLE READ the gaps between them too.
  • The binary log records each committed change. Replicas and point-in-time recovery replay it.
Sessiona transactionBuffer poolpages in memoryDiskredo, binlog, dataReplicaanother serverUPDATE changes a pageCOMMIT syncs the logsflush at a checkpointrecovery replays redobinlog replayed
UPDATE changes a page in memory. COMMIT waits until the redo log and binlog are on disk. Pages follow at checkpoints. Recovery and replicas replay the logs.

InnoDB & storage engines

how MySQL stores and caches a table

What MySQL gives you

Say what MySQL isAn open-source relational database that Oracle releases under the GPL.
Name its key featuresACID transactions, a storage engine per table, replication, partitioning and full-text search.

The InnoDB buffer pool

Size InnoDB's cache of data and index pagesinnodb_buffer_pool_size = 12GThe default is 128MB. Dedicated servers often give it up to 80% of RAM.
Resize it without a restartSET GLOBAL innodb_buffer_pool_size = 8589934592;

TipNew pages enter the old sublist, three eighths of the pool, so a table scan rarely pushes out hot pages.

TrapA pool that's too large can make the server swap.

Column types & keys

strings, numbers, JSON, keys and normal forms

Strings and numbers

Store a code that's always the same lengthcountry CHAR(2)MySQL pads it with spaces and strips them on read.
Store text that varies in lengthname VARCHAR(100)It stores only the characters used, plus a length prefix.
Store money exactlyprice DECIMAL(10,2)FLOAT and DOUBLE are approximate.
Pick the smallest integer that fitsqty SMALLINT UNSIGNED1, 2, 3, 4 or 8 bytes, from TINYINT to BIGINT.

TrapVARCHAR(n) counts characters, up to 4 bytes each in utf8mb4. A row's columns share 65,535 bytes.

AUTO_INCREMENT and upserts

Number new rows automaticallyid BIGINT AUTO_INCREMENT PRIMARY KEYOne per table, and it must be indexed.
Get the id your insert just madeSELECT LAST_INSERT_ID();Per connection, so other sessions can't change it.
Insert, or update on a duplicate keyON DUPLICATE KEY UPDATE n = new.nFrom 8.0.19, AS new after VALUES names the incoming row.

TipInnoDB keeps the counter across restarts. Inserts fail once it reaches the type's maximum.

TrapGaps are normal. A rolled-back insert or an updating upsert can use up a number, and InnoDB never reuses it.

utf8mb4 and collations

Store any Unicode text, emoji includedCHARACTER SET utf8mb4The default, with the collation utf8mb4_0900_ai_ci.
Make the client send utf8mb4 tooSET NAMES utf8mb4;
Explain VARCHAR(191) in old schemasOld COMPACT tables cap an index at 767 bytes, or 191 utf8mb4 characters.

TipIn a collation name, _ci ignores case and _ai ignores accents. _cs, _as and _bin are stricter.

TrapMySQL's utf8 is utf8mb3, 3 bytes at most. Emoji inserts fail in strict mode, and turn into ? without it.

JSON columns

Read a field as JSON, or as plain textattrs->'$.brand'attrs->>'$.brand'->> also removes the quotes.
Change one field in placeJSON_SET(attrs, '$.price', 34.99)MySQL rewrites only that part when the new value is no larger.
Index one fieldbrand VARCHAR(50) AS (attrs->>'$.brand')Index this generated column. Arrays need a multi-valued index (8.0.17+).

TipMySQL rejects invalid JSON with an error, and stores documents in a binary format.

Foreign keys and normal forms

Point a column at a row in another tableFOREIGN KEY (user_id) REFERENCES users(id)MySQL indexes user_id if needed.
Delete child rows along with their parentON DELETE CASCADESET NULL keeps them and clears the key.
Skip key checks during a bulk loadSET foreign_key_checks = 0;MySQL never checks the rows loaded meanwhile.
Reach third normal form (3NF)Atomic values, and non-key columns depend on the whole key, never on each other.
Go one step stricter than 3NFBoyce-Codd normal form (BCNF). Every determinant is a candidate key.

TipNO ACTION, the default, acts like RESTRICT in InnoDB. InnoDB rejects SET DEFAULT.

TrapOnly InnoDB and NDB enforce foreign keys. MyISAM accepts the clause and ignores it.

Indexes & EXPLAIN

find rows fast, and read the query plan

Clustered and secondary indexes

secondaryemail + idclusteredwhole row
email lookupThe secondary index leaf gives the primary key. The clustered index then finds the row.
Answer a query from the index aloneADD INDEX (status, total)A covering index. EXPLAIN shows Using index.
Pick a primary key that inserts fastAn ordered one, like AUTO_INCREMENT. Random keys can leave pages half full.
Store UUIDs in time orderUUID_TO_BIN(UUID(), 1)The 1 reorders the time parts so new values sort together.

TipEvery secondary index stores the primary key, so a long primary key makes every index bigger.

TrapWith no primary key or UNIQUE NOT NULL index, InnoDB clusters on a hidden row ID.

Create indexes

Index a column you filter or join onCREATE INDEX ix_email ON users (email);
Index two columns togetherADD INDEX (country, city)It serves country alone, but not city alone.
Search for words in textADD FULLTEXT (body)Query it with MATCH ... AGAINST.
Index an expression (8.0.13+)ADD INDEX ((LOWER(email)))
Test dropping an index without dropping itALTER INDEX ix_email INVISIBLEThe optimizer ignores it until you set VISIBLE.

TipMEMORY tables support hash indexes. InnoDB builds a B-tree even if you ask for USING HASH.

TrapEvery index slows writes and takes space. Drop the ones sys.schema_unused_indexes lists.

EXPLAIN and slow queries

Show the plan without running the queryEXPLAIN
Run the query and see real times and rows (8.0.18+)EXPLAIN ANALYZE
Rank the type column's values, best to worstconst, eq_ref, ref, range, index, then ALL, a full table scan.
Spot extra work in ExtraLook for Using filesort and Using temporary. Using index means the index covers the query.
Let an index serve a date filterCompare the bare column with a range. YEAR(col) = 2023 hides it from the index.
Choose between IN and EXISTSEither works. Since 8.0.16, both get the same semijoin rewrite.

Traprows is an estimate, and InnoDB's can be off. EXPLAIN ANALYZE shows the real count.

Grouping, joins & subqueries

MySQL's rules for GROUP BY, LIMIT, joins and subqueries

GROUP BY, HAVING and LIMIT

Count rows, and values that aren't NULLCOUNT(*)COUNT(bonus)
Combine a group's values into one stringGROUP_CONCAT(name ORDER BY name)MySQL cuts it at group_concat_max_len, 1,024 bytes by default.
Filter rows, then groupsWHERE salary > 50000HAVING COUNT(*) > 5MySQL also lets HAVING use a select-list alias.
Get page 3 at 10 rows a pageLIMIT 10 OFFSET 20LIMIT 20, 10The comma form puts the offset first.
Page deep into a large tableWHERE id > 1020 ORDER BY id LIMIT 10Keyset pagination. A large offset still reads every skipped row.

TipOrder pages by a unique key. Tied rows come back in any order, so pages can repeat or skip rows.

TrapONLY_FULL_GROUP_BY is on by default. It rejects a selected column that isn't grouped, aggregated or determined by the grouped columns.

Joins and subqueries

Get a full outer joinMySQL has none. Combine a LEFT JOIN and a RIGHT JOIN with UNION.
Tell JOIN from CROSS JOINIn MySQL they're the same. With ON you get matching rows, and without it every pair.
Pair each employee with their managerFROM emp e LEFT JOIN emp m ON e.manager_id = m.idA self-join. LEFT JOIN keeps employees with no manager.
Walk a tree of any depthWITH RECURSIVEIt stops at 1,000 levels, the cte_max_recursion_depth default.
Name subqueries by what they returnScalar returns one value, row one row, and table a set of rows.
Select from a subquery in FROMThat's a derived table, and MySQL requires an alias for it.

TipMySQL rewrites many IN and EXISTS subqueries as semijoins, which return each outer row once.

Transactions & locking

isolation, MVCC, locks and deadlocks

Transactions and autocommit

Run statements as one unitSTART TRANSACTION;COMMIT;ROLLBACK;
Check whether autocommit is onSELECT @@autocommit;It's on by default, so each statement commits by itself.
Name the four ACID guaranteesAtomicity, consistency, isolation and durability.

TrapDDL such as ALTER TABLE commits the open transaction, so a later ROLLBACK can't undo it.

Isolation levels

READ UNCOMMITTEDRead other sessions' uncommitted changes
READ COMMITTEDTake a new snapshot for every read
REPEATABLE READKeep the first read's snapshot, the default
SERIALIZABLEWith autocommit off.Make plain reads take shared locks
preventedcan happen

TipSet it with SET SESSION TRANSACTION ISOLATION LEVEL, and read @@transaction_isolation.

TrapInnoDB's REPEATABLE READ also stops phantom rows, using next-key locks. READ COMMITTED locks fewer gaps, so it deadlocks less.

MVCC and consistent reads

WriterUPDATECOMMITRR readerreads v1reads v1RC readerreads v1reads v2time

Both readers see v1 before the COMMIT, and neither waits. After it, REPEATABLE READ (RR) keeps its first snapshot, and READ COMMITTED (RC) sees v2.

Read rows without taking locksSELECT * FROM accounts WHERE id = 1;
Name the hidden fields on every rowDB_TRX_ID, the last transaction to change the row, and DB_ROLL_PTR, which points to its undo record.
Say when old versions go awayPurge removes them once no snapshot needs them.

TrapA long-running transaction, even a read-only one, holds back purge, and the undo tablespace grows.

Locks and deadlocks

Lock rows as you read themFOR UPDATEFOR SHARE
Stop inserts into a rangeA gap lock. A next-key lock covers a record plus the gap before it.
Avoid table locks on AUTO_INCREMENT insertsinnodb_autoinc_lock_mode = 2The default. Mode 1 takes the lock for bulk inserts, and mode 0 for every insert.
See the most recent deadlockSHOW ENGINE INNODB STATUS;innodb_print_all_deadlocks logs every one.
Prevent most deadlocksLock rows in the same order everywhere, and keep transactions short.
Fail at once if a row is lockedFOR UPDATE NOWAITOr lower innodb_lock_wait_timeout, 50 seconds by default.

TipWithout an index on the WHERE column, an UPDATE scans the whole table and locks every row.

TrapInnoDB rolls back one transaction, the victim, with a deadlock error. Your code must retry the whole transaction.

Logs, backups & replication

durability, restores and copies on other servers

Redo, undo and binary logs

Name the log that brings commits back after a crashThe redo log. InnoDB flushes it at each commit and replays it after a crash.
Roll back, and keep old row versionsThe undo log. Rollback and MVCC reads both use it.
Feed replicas and point-in-time recoveryThe binary log. The server writes it, not InnoDB.
Size the redo log (8.0.30+)innodb_redo_log_capacity = 2GIt replaced innodb_log_file_size.

TipA checkpoint marks how far changed pages are safely on disk. InnoDB then truncates the redo log before it.

Back up and restore

Dump a database without locking InnoDB tablesmysqldump --single-transaction shop > shop.sqlIt reads one consistent snapshot.
Restore a dumpmysql shop < shop.sqlThe dump drops and recreates each table first.
Back up a large server fastUse Percona XtraBackup or MySQL Enterprise Backup. Restores are much faster.
Recover to a point after the last backupmysqlbinlog binlog.000042 | mysqlRestore the backup, then replay the binary logs.

TipTake backups on a replica to keep the load off the source.

TrapCopying a running InnoDB server's files doesn't give a working backup. Use a hot backup tool.

Replication

Explain how a replica stays in syncEach replica copies the binary log into a relay log and replays it.
Point a replica at its source (8.0.23+)CHANGE REPLICATION SOURCE TO SOURCE_HOST='db1';START REPLICA;Also set the login, SOURCE_SSL=1 and binlog position, or use global transaction IDs (GTIDs).
See the source's binlog position (8.2+)SHOW BINARY LOG STATUS;On 8.0, run SHOW MASTER STATUS.
Make a commit wait for a replicaSemisynchronous replication. It falls back to asynchronous after a timeout.
Fail over automaticallyGroup Replication elects a new primary. Multi-primary mode lets every member write.

TipReplication is asynchronous by default, so a replica may miss the last commits when the source crashes.

TrapMySQL 8.4 removed CHANGE MASTER TO, START SLAVE and SHOW MASTER STATUS. Old scripts that use them fail.

Tuning & monitoring

settings, connections and slow statements

Server settings and pooling

Keep every commit through a crashinnodb_flush_log_at_trx_commit = 1The default. With 2, a crash can lose the last second of commits.
Sync the binary log at each commitsync_binlog = 1
Raise the connection limitmax_connections = 500The default is 151.
Say why apps pool connectionsEach connection costs a login and a server thread. A pool pays that cost once per connection.
Stop the server closing pooled connectionsKeep the pool's maximum lifetime a few seconds below wait_timeout.
Cache query resultsIn the application, with Redis or Memcached. MySQL 8.0 removed the query cache.

TipKeep the pool small. A small pool with waiting threads is faster than a huge one.

TrapSET GLOBAL lasts until a restart. Use SET PERSIST, or put it in my.cnf.

Monitor a running server

Log statements slower than 2 secondsSET GLOBAL slow_query_log = 'ON';SET GLOBAL long_query_time = 2;The default is 10 seconds.
Find the statements that take the most timeSELECT * FROM sys.statement_analysis LIMIT 10;It reads the Performance Schema, on by default.
See what every session is runningSHOW FULL PROCESSLIST;

TipTune in layers. Queries and indexes first, then memory and logging, then hardware.

Delete, alter & partition

remove data, and change big tables without long locks

DELETE, TRUNCATE or DROP

Remove the rows a condition matchesDELETE FROM users WHERE age < 18;It fires delete triggers, and a transaction can roll it back.
Empty the whole table fastTRUNCATE TABLE users;It resets AUTO_INCREMENT and fires no triggers.
Remove the table itselfDROP TABLE users;

TipTRUNCATE fails on an InnoDB table that another table's foreign key points to.

TrapTRUNCATE and DROP commit at once, so ROLLBACK can't bring the data back.

Change a large table safely

Delete old rows in batchesDELETE FROM logs WHERE id < 90000 LIMIT 1000;Commit and repeat. An UPDATE batch must skip rows already done.
Add a column without a table copy (8.0.12+)ADD COLUMN phone VARCHAR(20), ALGORITHM=INSTANTMySQL picks INSTANT by default when it can.
Alter a table while writes continueALGORITHM=INPLACE, LOCK=NONEIf MySQL can't, it fails at once.
Run a change that needs a table copyUse pt-online-schema-change or gh-ost. Both copy rows in chunks.

TrapOnline DDL still takes a brief exclusive metadata lock. It waits for open transactions, and new queries queue behind it.

Partition a large table

Split a table by yearPARTITION BY RANGE (YEAR(sale_date))
Split by a list or a hashPARTITION BY LIST (store_id)PARTITION BY HASH (id) PARTITIONS 4
Remove a year of data fastALTER TABLE sales DROP PARTITION p2020;

TipPartition pruning skips partitions the WHERE clause rules out. So partition on the column your queries filter by.

TrapEvery unique key, the primary key too, must include the partitioning columns. Partitioned tables can't have foreign keys.

Views, procedures & triggers

code and queries the server keeps for you

Views

Save a query under a nameCREATE VIEW active_users AS SELECT id, name FROM users WHERE active;A view stores no data. Grant it instead of the table to hide columns.
Keep base-table indexes working through a viewMySQL merges the view into your query when it can. A TEMPTABLE view can't use them.

TrapA view with GROUP BY or DISTINCT uses TEMPTABLE, so you can't update through it.

Stored procedures and triggers

Write a procedure in the mysql clientDELIMITER //The client then ends statements at //, not at each ; in the body.
Say why teams use stored proceduresOne CALL replaces many round trips. MySQL doesn't precompile them, though.
Give access to data only through a procedureGrant EXECUTE only. The procedure runs with its DEFINER account's privileges.
Return a value you can use in a queryWrite a stored function instead.
Fix a value before the row is savedSET NEW.email = LOWER(NEW.email);In a BEFORE INSERT or BEFORE UPDATE trigger.
Log every change to an audit tableAn AFTER UPDATE trigger. OLD holds the row before, and NEW after.

TipIf a trigger fails, its statement fails too, and InnoDB rolls both back.

TrapCascaded foreign key actions don't fire triggers.

Found this useful? Pass it on.
Pro · $10/mo

This sheet stays free. With Pro, you can:

  • Every answer on this sheet. Read the full answer to all 42 questions it links, not just the 5 free ones.
  • My sheet. The cards you star and the ones you miss in Quiz me, from every sheet, on one page to quiz and print.
  • PDFs. Download this sheet and My sheet, laid out for A4, with every question a link to its answer.
  • The rest of Pro. Every answer in the question library, and 1,000 AI credits a month.