- Фундамент (ACID, CAP, BASE)
- Транзакции и блокировки
- Проектирование (Normal forms, Trees)
- Производительность (Indexes, Optimisation)
- Программирование в БД
- Explain Analyze
- Connection Pooling
ACID - это набор свойств, гарантирующих, что транзакции в базе данных будут выполняться надежно. Эти свойства:
- Atomicity (Атомарность): Все операции в транзакции рассматриваются как единое целое. Либо все операции успешно завершаются, либо ни одна из них не применяется.
- Consistency (Согласованность): Транзакция переводит базу данных из одного согласованного состояния в другое согласованное состояние.
- Isolation (Изолированность): Параллельные транзакции не влияют друг на друга. Существует несколько уровней изоляции:
- Read Uncommitted (Чтение неподтвержденных данных): Самый низкий уровень, позволяет читать неподтвержденные изменения других транзакций (dirty reads).
- Read Committed (Чтение подтвержденных данных): Транзакция видит только те изменения, которые были зафиксированы до начала транзакции. Предотвращает dirty reads.
- Repeatable Read (Повторяемость чтения): Гарантирует, что если транзакция читает одну и ту же строку несколько раз, она получит одинаковое значение. Предотвращает dirty и non-repeatable reads.
- Serializable (Сериализуемость): Высший уровень изоляции, обеспечивает полную изоляцию транзакций, как будто они выполняются последовательно. Предотвращает dirty, non-repeatable reads и phantom reads.
Проблемы, которые решают уровни изоляции:
- Dirty Read: Чтение неподтвержденных изменений другой транзакции.
- Non-repeatable read: При повторном чтении одной и той же строки в рамках одной транзакции, значения могут отличаться.
- Phantom read: При повторном выполнении одного и того же запроса в одной транзакции, результат может содержать новые строки.
CAP-теорема утверждает, что в распределенной системе хранения данных можно одновременно достичь только двух из трех свойств:
- Consistency (Согласованность): Все узлы системы видят одинаковые данные в одно и то же время.
- Availability (Доступность): Система продолжает работать и отвечать на запросы, даже если некоторые узлы недоступны.
- Partition Tolerance (Устойчивость к разделению): Система продолжает работать, даже если между узлами теряются сообщения или происходит сетевое разделение.
Почему нельзя получить всё сразу: При сетевом разделении (partition) система должна выбрать между согласованностью (consistency) и доступностью (availability).
Разница между SQL и NoSQL:
- SQL (обычно CA/CP): Стремятся к строгой согласованности и доступности при нормальной работе (CA), либо к согласованности и устойчивости к разделению (CP).
- NoSQL (AP): Часто жертвуют согласованностью в пользу доступности и устойчивости к разделению (AP).
BASE противоположно ACID и представляет собой подход к дизайну распределенных систем:
- Basically Available (Базовая доступность): Система доступна большую часть времени.
- Soft state (Мягкое состояние): Состояние системы может изменяться со временем, даже без входных данных.
- Eventually consistent (В конечном итоге согласованность): Система достигает согласованности в течение некоторого времени после прекращения входных данных.
Пессимистичные блокировки:
- Предполагают, что конфликты будут происходить, и блокируют ресурсы заранее.
- Используются при высоком уровне конкурентности.
- Примеры:
SELECT ... FOR UPDATE,SELECT ... FOR SHARE - Преимущества: Гарантируют отсутствие конфликтов.
- Недостатки: Могут привести к снижению производительности из-за блокировок.
Оптимистичные блокировки:
- Предполагают, что конфликты редки, и проверяют наличие конфликтов только при фиксации изменений.
- Используются при низком уровне конкурентности.
- Примеры: Версионирование данных, контрольные суммы.
- Преимущества: Лучшая производительность при низкой конкурентности.
- Недостатки: Возможность отката транзакций при обнаружении конфликта.
Deadlock (взаимоблокировка) - ситуация, когда два или более процесса ожидают освобождения ресурсов, занятых друг другом, создавая цикл ожидания.
Как возникают:
- Процесс A захватывает ресурс X и ожидает ресурс Y
- Процесс B захватывает ресурс Y и ожидает ресурс X
- Оба процесса блокируются вечно
Как диагностировать:
- Использование системных представлений (например,
pg_stat_activityв PostgreSQL) - Анализ логов БД
- Использование специализированных инструментов диагностики
- Встроенные механизмы обнаружения взаимоблокировок (в PostgreSQL автоматическое обнаружение и устранение)
Методы предотвращения:
- Упорядочивание захвата ресурсов (всегда захватывать в одном порядке)
- Использование таймаутов
- Минимизация времени удержания блокировок
Первая нормальная форма (1NF):
- Каждая ячейка содержит только одно значение
- Все значения в столбце одного типа
- Столбцы имеют уникальные имена (и смысловое значение)
- Не дублирующих строк
Вторая нормальная форма (2NF):
- Удовлетворяет требованиям 1NF
- Есть ключь, по которму можно однозначно идентифицировать строку.
- Все неключевые атрибуты полностью функционально зависят от первичного ключа (не частично). Или от полного ключа если ключ составной.
Третья нормальная форма (3NF):
- Удовлетворяет требованиям 2NF
- Нет транзитивных зависимостей (неключевые атрибуты не зависят от других неключевых атрибутов)
Нормальная форма Бойса - Кодда(BCNF):
- Удовлетворяет требованиям 3NF
- Ключевые атрибуты составного ключа не должны зависеть от неключевых атрибутов. Актуально только для таблиц у которых первичный ключ составной.
Когда осознанно пора делать денормализацию для производительности:
- Когда запросы требуют частого объединения многих таблиц
- Для аналитических нагрузок (OLAP)
- Для кэширования часто используемых вычислений
- При наличии четких требований к производительности чтения
Adjacency List (Список смежности):
- Каждый узел содержит ссылку на родителя
- Плюсы: Простота вставки/обновления, компактность
- Минусы: Требуется рекурсивный запрос или CTE для получения всей ветви дерева
Path Enumeration (Перечисление пути):
- Каждый узел содержит путь ко всем своим предкам
- Плюсы: Легко находить потомков и предков
- Минусы: Длинные пути могут быть неэффективны, трудности при перемещении узлов
Nested Sets (Вложенные множества):
- Каждый узел имеет левое и правое значение, определяющее диапазон
- Плюсы: Легко находить всех потомков
- Минусы: Сложные вставки и удаления, требуется перестройка при изменениях
Closure Table (Таблица замыкания):
- Отдельная таблица хранит все связи "предок-потомок" на всех уровнях
- Плюсы: Быстрый поиск потомков и предков
- Минусы: Требует больше места, сложнее поддерживать целостность
B-Tree (Balanced Tree):
- Стандартный тип индекса в большинстве СУБД
- Эффективен для точного поиска, диапазонных запросов и сортировки
- Поддерживает равенство и операторы сравнения (<, <=, >, >=)
Hash:
- Эффективен только для операций равенства (=)
- Быстрее B-tree для точного совпадения
- Не поддерживает диапазонные запросы
GIN (Generalized Inverted Index):
- Для данных с несколькими значениями в одной ячейке (массивы, JSONB)
- Используется для поиска по ключам/значениям в JSONB
- Подходит для полнотекстового поиска
GiST (Generalized Search Tree):
- Поддерживает пользовательские типы данных и операторы
- Используется для геометрических данных, текстового поиска, поиска ближайших соседей
Sequential Scan:
- Сканирование всей таблицы построчно
- Используется, когда необходимо прочитать большую часть строк
- Может быть эффективнее при маленьких таблицах
Index Scan:
- Использует индекс для нахождения строк
- Затем обращается к основной таблице для получения полных данных
- Эффективен для выборки небольшого числа строк
Index Only Scan:
- Все необходимые данные находятся в самом индексе
- Не требует доступа к основной таблице
- Наиболее эффективный метод сканирования
Partitioning (Разбиение):
- Логическое деление большой таблицы на более мелкие части внутри одной БД
- Все части находятся на одном сервере
- Управление осуществляется на уровне СУБД
- Примеры: по диапазону дат, по списку значений
Sharding (Шардинг):
- Физическое распределение данных по разным серверам
- Каждый шард - отдельная БД на отдельном сервере
- Требует управления на уровне приложения
- Используется для масштабирования по горизонтали
Stored Procedures (Хранимые процедуры):
- Могут возвращать несколько значений или вообще не возвращать
- Не обязательно возвращают значение
- Могут содержать операторы DML (INSERT, UPDATE, DELETE)
- Не могут использоваться в выражениях SQL
- Не обязательно входят в транзакцию (в некоторых СУБД)
Functions (Функции):
- Всегда возвращают значение
- Не должны иметь побочных эффектов (в идеале)
- Могут использоваться в выражениях SQL
- Обычно запрещены операторы DML (в PostgreSQL, например)
- Всегда входят в транзакцию
Views (Представления):
- Виртуальные таблицы, определяемые запросом
- Данные не хранятся физически
- При каждом обращении выполняется исходный запрос
- Всегда актуальные данные
Materialized Views (Материализованные представления):
- Физически хранят результат запроса
- Требуют периодического обновления (REFRESH)
- Быстрее для сложных запросов
- Могут использовать индексы
- Используются для кэширования результатов сложных запросов
Explain Analyze - критический навык для Senior разработчика, позволяющий анализировать план выполнения запроса.
Как читать план запроса:
- Seq Scan: Последовательное сканирование всей таблицы
- Index Scan: Использование индекса для поиска строк
- Index Only Scan: Поиск только в индексе, без обращения к таблице
- Bitmap Heap Scan: Используется после Bitmap Index Scan для извлечения строк
- Cost: Оценочные затраты (начальные / общие)
- Actual time: Реальное время выполнения
- Rows: Количество обработанных строк
- Width: Средний размер строки в байтах
Ключевые показатели:
- Planned vs Actual: Сравнение оценок планировщика с реальностью
- Node Execution Count: Количество выполнений узла
- Buffers: Использование буферов (shared, temp, hit ratio)
Connection Pooling (Пул соединений) - механизм управления соединениями с БД, позволяющий повторно использовать существующие соединения.
Зачем нужен:
- Ограничение количества одновременных соединений к БД
- Снижение накладных расходов на установку/закрытие соединений
- Защита от перегрузки БД большим количеством соединений
Почему Node.js не должен открывать новое соединение на каждый запрос:
- Установка соединения требует времени (TCP handshake, аутентификация)
- Каждое соединение потребляет ресурсы на стороне БД
- Ограниченное количество одновременных соединений в БД
- Состояние соединения может быть переиспользовано
Популярные пулы соединений:
- PgBouncer (для PostgreSQL): Легковесный пул с поддержкой различных режимов
- pg-pool (для Node.js): Пул соединений для PostgreSQL
- HikariCP (для Java): Высокопроизводительный пул соединений
Режимы работы пула:
- Session pooling: Соединение используется одним клиентом за сессию
- Transaction pooling: Соединение используется для одной транзакции
- Statement pooling: Соединение используется для одного SQL-оператора
Согласованное состояние базы данных (consistency) — это свойство, при котором данные соответствуют всем установленным правилам, ограничениям (целостности, типам данных, внешним ключам) и внутренней логике системы после выполнения транзакций. База данных всегда переходит из одного корректного состояния в другое; если транзакция нарушает правила, она отменяется.
Ключевые аспекты согласованности:
При нарушении согласованности система может работать некорректно, поэтому при сбоях выполняется откат (rollback) до предыдущего валидного состояния.