Quick Reference
| Topic | File |
|---|---|
| Index design deep dive | indexes.md |
| Transactions and locking | transactions.md |
| Query optimization | queries.md |
| Production config | production.md |
Character Set Traps
utf8is broken—only 3 bytes, can't store emoji; always useutf8mb4utf8mb4_unicode_cifor case-insensitive sorting;utf8mb4_binfor exact byte comparison- Collation mismatch in JOINs kills performance—ensure consistent collation across tables
- Connection charset must match:
SET NAMES utf8mb4or connection string parameter - Index on utf8mb4 column larger—may hit index size limits; consider prefix index
Index Differences from PostgreSQL
- No partial indexes—can't
WHERE active = truein index definition - No expression indexes until MySQL 8.0.13—must use generated columns before that
- TEXT/BLOB needs prefix length:
INDEX (description(100))—without length, error - No INCLUDE for covering—add columns to index itself:
INDEX (a, b, c)to cover c - Foreign keys auto-indexed only in InnoDB—verify engine before assuming
UPSERT Patterns
INSERT ... ON DUPLICATE KEY UPDATE—not standard SQL; needs unique key conflictLAST_INSERT_ID()for auto-increment—no RETURNING clause like PostgreSQLREPLACE INTOdeletes then inserts—changes auto-increment ID, triggers DELETE cascade- Check affected rows: 1 = inserted, 2 = updated (counter-intuitive)
Locking Traps
SELECT ... FOR UPDATElocks rows—but gap locks may lock more than expected- InnoDB uses next-key locking—prevents phantom reads but can cause deadlocks
- Lock wait timeout default 50s—
innodb_lock_wait_timeoutfor adjustment FOR UPDATE SKIP LOCKEDexists in MySQL 8+—queue pattern- InnoDB default isolation is REPEATABLE READ, not READ COMMITTED like PostgreSQL
- Deadlocks are expected—code must catch and retry, not just fail
GROUP BY Strictness
sql_modeincludesONLY_FULL_GROUP_BYby default in MySQL 5.7+- Non-aggregated columns must be in GROUP BY—unlike old MySQL permissive mode
ANY_VALUE(column)to silence error when you know values are same- Check sql_mode on legacy databases—may behave differently
InnoDB vs MyISAM
- Always use InnoDB—transactions, row locking, foreign keys, crash recovery
- MyISAM still default for some system tables—don't use for application data
- Check engine:
SHOW TABLE STATUS—convert withALTER TABLE ... ENGINE=InnoDB - Mixed engines in JOINs work but lose transaction guarantees
Query Quirks
LIMIT offset, countdifferent order than PostgreSQL'sLIMIT count OFFSET offset!=and<>both work; prefer<>for SQL standard- No transactional DDL—
ALTER TABLEcommits immediately, can't rollback - Boolean is
TINYINT(1)—TRUE/FALSEare just 1/0 IFNULL(a, b)instead ofCOALESCEfor two args—though COALESCE works
Connection Management
wait_timeoutkills idle connections—default 8 hours; pooler may not noticemax_connectionsdefault 151—often too low; each uses memory- Connection pools: don't exceed max_connections across all app instances
SHOW PROCESSLISTto see active connections—kill long-running withKILL <id>
Replication Awareness
- Statement-based replication can break with non-deterministic functions—UUID(), NOW()
- Row-based replication safer but more bandwidth—default in MySQL 8
- Read replicas have lag—check
Seconds_Behind_Masterbefore relying on replica reads - Don't write to replica—usually read-only but verify
Performance
EXPLAIN ANALYZEonly in MySQL 8.0.18+—older versions just EXPLAIN without actual times- Query cache removed in MySQL 8—don't rely on it; cache at application level
OPTIMIZE TABLEfor fragmented tables—locks table; use pt-online-schema-change for big tablesinnodb_buffer_pool_size—set to 70-80% of RAM for dedicated DB server