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 comparisonCollation mismatch in JOINs kills performance—ensure consistent collation across tables
Connection charset must match:
SET NAMES utf8mb4or connection string parameterIndex 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 definitionNo expression indexes until MySQL 8.0.13—must use generated columns before that
TEXT/BLOB needs prefix length:
INDEX (description(100))—without length, errorNo INCLUDE for covering—add columns to index itself:
INDEX (a, b, c)to cover cForeign 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 cascadeCheck affected rows: 1 = inserted, 2 = updated (counter-intuitive)
Locking Traps
SELECT ... FOR UPDATElocks rows—but gap locks may lock more than expectedInnoDB uses next-key locking—prevents phantom reads but can cause deadlocks
Lock wait timeout default 50s—
innodb_lock_wait_timeoutfor adjustmentFOR UPDATE SKIP LOCKEDexists in MySQL 8+—queue patternInnoDB 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 sameCheck 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=InnoDBMixed 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 standardNo transactional DDL—
ALTER TABLEcommits immediately, can't rollbackBoolean is
TINYINT(1)—TRUE/FALSEare just 1/0IFNULL(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 memoryConnection 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 readsDon't write to replica—usually read-only but verify
Performance
EXPLAIN ANALYZEonly in MySQL 8.0.18+—older versions just EXPLAIN without actual timesQuery 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