MySQL
InnoDB, replication, query optimization, common patterns.
Theory
MySQL is the world's most deployed open-source relational database. InnoDB is the default storage engine — ACID-compliant with row-level locking, MVCC, foreign keys, and crash recovery via redo logs. MyISAM is legacy (no transactions) — avoid for new work.
Transaction isolation: READ COMMITTED (default in many DBs) sees committed changes from other transactions mid-query. REPEATABLE READ is MySQL InnoDB default — consistent snapshot for transaction duration, prevents non-repeatable reads but can have phantom reads (gap locks address this).
EXPLAIN output columns: type (ALL=full scan, ref=index lookup, range=index range scan), possible_keys, key (index used), key_len (bytes used — composite index prefix), rows (estimated), Extra (Using index = covering index, Using filesort = extra sort pass, Using temporary = temp table).
Covering index includes all columns needed by a query — InnoDB secondary indexes leaf nodes store primary key; if all SELECT columns are in index, no table lookup (Using index in Extra). Design indexes for your actual query patterns, not every column.
Binlog (binary log) records all data changes for replication and point-in-time recovery. Row-based format (binlog_format=ROW) is safest for consistency. Primary replicates to replicas asynchronously — replicas may lag (replication lag). Semi-sync replication waits for one replica ack before commit.
Slow query analysis: enable slow_query_log with long_query_time threshold. pt-query-digest (Percona Toolkit) aggregates slow.log into normalized patterns ranked by total time. Focus on queries with highest Query_time × count product.
Stored procedures run logic inside the DB — reduce round trips but harder to version/deploy than app code. Use for simple batch operations; avoid complex business logic in procedures for microservice architectures.
Query optimization workflow: EXPLAIN the slow query, check indexes on WHERE/JOIN/ORDER BY columns, verify cardinality statistics (ANALYZE TABLE), avoid SELECT *, rewrite OR conditions as UNION for index use, and consider read replicas for analytics load.
Architecture Diagram
Users / clients
|
MySQL
|
Core services
|
Data + observabilityExamples
EXPLAIN SELECT id, email, status
FROM users
WHERE status = 'active' AND created_at > '2024-01-01'
ORDER BY created_at DESC
LIMIT 50;
CREATE INDEX idx_users_status_created
ON users (status, created_at, email, id);
-- Covering index: all SELECT columns included → "Using index" in Extra
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- Isolation demo: REPEATABLE READ
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1; -- 500
-- (another session updates balance to 400 and commits)
SELECT balance FROM accounts WHERE id = 1; -- still 500
# Enable slow log
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
# Analyze with Percona Toolkit
pt-query-digest /var/log/mysql/slow.log | head -40
# Check replication lag
SHOW REPLICA STATUS\G
# Seconds_Behind_Source (or Seconds_Behind_Master)
Key Concepts
Interview Questions
Why is InnoDB preferred over MyISAM?
InnoDB supports transactions, row-level locking, foreign keys, and crash recovery. MyISAM has table-level locks and no transactions — corrupts easily on crash. InnoDB is default since MySQL 5.5. Use InnoDB for all production tables.
Explain READ COMMITTED vs REPEATABLE READ in MySQL.
READ COMMITTED: each statement sees latest committed data — non-repeatable reads possible. REPEATABLE READ (InnoDB default): consistent snapshot from transaction start — InnoDB uses next-key locks to prevent phantoms in most cases. SERIALIZABLE is strictest but slowest.
How do you read MySQL EXPLAIN key_len and Extra?
key_len shows bytes of index used — partial composite index usage visible here. Extra: Using index = covering index (no table access). Using filesort = extra sort pass (add index for ORDER BY). Using temporary = temp table created. type=ALL means full table scan — add index.
What is a covering index?
Index containing all columns needed by query — InnoDB secondary index leaves include PK columns. SELECT columns all in index → engine never touches table data. Dramatically reduces I/O for read-heavy queries. Design indexes around query patterns, not individual columns in isolation.
How does MySQL binlog replication work?
Primary writes changes to binlog. Replica I/O thread fetches binlog events; SQL thread applies them. Async by default — replica can lag. ROW format logs actual row changes (safest). Monitor Seconds_Behind_Source. GTID ensures consistent failover position across topology.
How do you find and fix slow queries?
Enable slow_query_log, run pt-query-digest for top patterns by total time. EXPLAIN each pattern. Add/adjust indexes, rewrite queries (avoid functions on indexed columns), increase innodb_buffer_pool_size if buffer pool hit rate low. Verify with EXPLAIN after changes.
FAANG: How do you handle 100x read traffic on MySQL?
Read replicas for read-heavy workloads (eventual consistency). Connection pooling (ProxySQL). Cache hot data in Redis. Shard by tenant/user_id when single instance saturates. Optimize top queries first — often 5 queries cause 80% load. Consider separating OLAP to columnar store.
What causes replication lag and how do you reduce it?
Replica applies changes slower than primary generates them — large transactions, missing indexes on replica, parallel replication disabled, network latency. Fix: break large transactions, enable replica_parallel_workers, optimize slow replica queries, use semi-sync for critical data, monitor lag alerting.
Best Practices
- Enable slow query log in staging with
long_query_time=0.5— find real offenders before production tuning. - Analyze EXPLAIN
rowsandExtra— "Using filesort" or "Using temporary" on large tables need indexes or query rewrites. - Use
ROWbinlog format for replication — STATEMENT mode breaks with non-deterministic functions. - Prefer
ALGORITHM=INPLACE, LOCK=NONEfor online DDL when InnoDB supports it — avoid full table copies. - Size InnoDB buffer pool to ~70% of RAM on dedicated DB servers — the dominant read performance lever.
- Use connection pooling (ProxySQL or app-side pool) — MySQL thread-per-connection does not scale like PgBouncer.
Common Mistakes
- Adding indexes without checking cardinality — low-cardinality columns (boolean, status) rarely help B-tree lookups.
- Using
SELECT *with JOINs — multiplies row width and prevents covering indexes. - Running schema migrations without pt-online-schema-change on huge tables — hours-long locks block writes.
- Ignoring deadlock logs — recurring deadlocks indicate transaction ordering bugs, not random noise.
- Mixing MyISAM and InnoDB — MyISAM has no crash recovery or foreign keys; legacy tables cause data loss.
Cheat Sheet
Practical Exercises
Create orders + customers tables. Write a 3-table JOIN missing an index. Add indexes on FK columns and compare EXPLAIN rows estimates.
Enable slow_query_log, run a workload, analyze with pt-query-digest. Fix the top query and remeasure.
Set up primary + replica with ROW binlog. Run a heavy UPDATE. Monitor Seconds_Behind_Master and tune batch size.