Базы данных
MySQL и InnoDB: отличия от PostgreSQL
Половина интернета работает на MySQL. Facebook хранит в нём пользовательский граф из 3 миллиардов людей. Shopify обрабатывает миллион транзакций в пик на MySQL. Зная его внутреннее устройство, можно превратить 10-секундный запрос в 10-миллисекундный.
- **Shopify**: MySQL + Vitess для шардинга - обрабатывает $500B GMV в год, >1 млн RPS в Black Friday
- **Twitter**: MySQL для основных данных (твиты, подписки) с multi-datacenter репликацией через binlog
- **GitHub**: тысячи MySQL серверов под управлением Orchestrator, автофailover за 30 секунд
InnoDB Storage Engine
InnoDB - дефолтный движок MySQL с 2010 года. До него MySQL использовал MyISAM, который не поддерживал транзакции и внешние ключи. Переход на InnoDB был революцией: появился ACID, row-level locking и crash recovery через redo log.
InnoDB хранит данные в **clustered index** - B-Tree, где листовые узлы содержат сами строки, а не указатели на них. Первичный ключ определяет физический порядок данных на диске. Это делает range-запросы по PK молниеносными, но вторичные индексы содержат значение PK как указатель, что добавляет один уровень поиска.
Twitter в 2010-х годах держал основные таблицы твитов на MySQL+InnoDB. При пиковой нагрузке (Super Bowl) они обрабатывали 143,199 твитов в секунду - всё на InnoDB с правильно настроенным buffer pool.
Как InnoDB хранит строки таблицы физически?
MySQL Replication
MySQL replication работает через binary log (binlog) - журнал всех изменений на master. Replica подключается к master, скачивает binlog events и применяет их. Исторически репликация была асинхронной: master не ждёт подтверждения от replica, что давало lag в секунды.
**Semi-synchronous replication** (доступна с MySQL 5.5) гарантирует, что хотя бы одна replica подтвердила получение события перед коммитом на master. Instagram использовал этот режим для защиты от потери данных при падении master.
Airbnb в 2018 году перешёл с асинхронной репликации на semi-sync после инцидента: при падении master replica отставала на 8 секунд, и 8 секунд транзакций были потеряны. После переключения потеря данных при failover стала нулевой.
Что гарантирует semi-synchronous replication в MySQL?
MySQL vs PostgreSQL
MySQL и PostgreSQL решают одну задачу, но с разной философией. MySQL исторически оптимизирован для web-workload: OLTP, простые запросы, горизонтальное масштабирование через репликацию. PostgreSQL - для complex queries, аналитики, extensibility (custom types, functions, FDW).
| Критерий | MySQL | PostgreSQL |
|---|---|---|
| MVCC | через undo log (в InnoDB) | через heap + visibility map |
| JSON | JSON тип (5.7+) | JSONB с GIN индексами |
| Full-text | FULLTEXT index | tsvector + GIN, лучше для RU |
| Window functions | с 8.0 | давно, полная поддержка |
| Репликация | встроенная + ProxySQL | streaming + logical |
| Кто использует | Facebook, Twitter, Shopify | Instagram, Apple, Reddit |
Instagram запустился на PostgreSQL, но их ключевая фид-таблица хранит 400 миллиардов строк - они не трогали PostgreSQL, докупали железо. Shopify в 2023 держит MySQL для транзакций (>10000 RPS) и PostgreSQL для аналитики и поисковых фич.
При каком сценарии PostgreSQL даёт преимущество над MySQL?
Clustered Index и его последствия
Clustered index в InnoDB - это не просто индекс, это физический порядок данных. Каждая таблица InnoDB имеет ровно один clustered index (всегда по PRIMARY KEY). Если PK не задан явно, InnoDB создаёт скрытый 6-байтный row ID.
Последствие: **вторичные индексы хранят значение PK**, а не физический адрес строки. При поиске по вторичному индексу InnoDB делает два lookup: сначала в secondary index (получает PK), потом в clustered index (получает строку). Это называется 'double lookup' или 'bookmark lookup'.
Covering index - способ избежать double lookup: если вторичный индекс содержит все нужные колонки, InnoDB не идёт в clustered index. INDEX (user_id, amount) покрывает запрос SELECT amount WHERE user_id = 123 полностью.
Почему UUID как PRIMARY KEY в InnoDB ухудшает производительность INSERT?
GTID: Global Transaction Identifiers
GTID (Global Transaction Identifier) - уникальный идентификатор каждой транзакции в формате `server_uuid:transaction_number`. Введён в MySQL 5.6. До GTID failover был болезненным: нужно было вручную найти позицию в binlog на новом master.
С GTID replica знает, какие транзакции она уже применила. При failover новый master и все replica автоматически синхронизируются - нет нужды указывать `MASTER_LOG_FILE` и `MASTER_LOG_POS` вручную. Это основа для автоматического failover через MHA, Orchestrator или MySQL Router.
GitHub использует Orchestrator + GTID для управления сотнями MySQL серверов. Автоматический failover занимает менее 30 секунд. До GTID failover требовал ручного вмешательства инженера и занимал 5-15 минут.
MySQL устарел и не подходит для высоких нагрузок
MySQL обрабатывает нагрузку Facebook (миллиарды запросов в день) и является основой Vitess - системы управления БД YouTube и Shopify
Предубеждение появилось из-за старого MyISAM и отсутствия некоторых фич SQL-стандарта. InnoDB + современные инструменты делают MySQL production-ready для любых масштабов.
Какую проблему решает GTID при MySQL failover?
Ключевые идеи
- **InnoDB clustered index** - строки хранятся в B-Tree по PK, монотонный BIGINT PK критичен для производительности INSERT
- **Binlog репликация** - основа масштабирования чтения; semi-sync гарантирует отсутствие потери данных при failover
- **GTID** - автоматическая синхронизация replica при смене master, основа для Orchestrator и автоматического failover
Связанные темы
MySQL - база для понимания репликации и шардинга на больших масштабах:
- Репликация БД — MySQL binlog репликация - классический пример асинхронной репликации
- Шардинг — Vitess добавляет шардинг поверх MySQL для горизонтального масштабирования
- Индексы B-Tree — InnoDB использует B-Tree для clustered и secondary индексов
Вопросы для размышления
- Если таблица orders растёт на 10 млн строк в день, какой тип PRIMARY KEY выбрать и почему?
- В чём компромисс между асинхронной и semi-synchronous репликацией с точки зрения latency и durability?
- Когда имеет смысл использовать MySQL вместо PostgreSQL, даже если в проекте уже есть PostgreSQL?