- Фундамент (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-оператора
Q: Explain Storing of Trees structures for PostgreSQL.
A: # Storing Tree Structures in PostgreSQL
PostgreSQL provides several approaches to store and manage hierarchical tree data structures. Here are the main methods:
1. Adjacency List Model (Most Common)
The simplest approach stores each node with a reference to its parent.
Basic Table Structure
Querying Operations
Get all children of a node:
2. Path Enumeration Model
Store the complete path to each node as a string.
Querying Path-Based Trees
3. Nested Set Model
Uses left and right values to represent tree structure.
Advantages of Nested Set:
4. Materialized Path with Additional Features
Enhanced approach combining benefits of different models:
Advanced Query Examples
Get Full Tree Hierarchy
Get Ancestors of a Node
Get Siblings
Performance Considerations
Indexing Strategy
Maintenance Operations
Best Practices
The adjacency list model is most commonly used due to its simplicity and flexibility, while path enumeration offers better performance for read-heavy scenarios.