Технологии

БД без боли: моя шпаргалка для собесов в Java. Часть 4

Всем привет! Я Senior Java Developer в банке, и за эти годы мне довелось пройти немало собеседований. Где-то было спокойно, где-то — как в допросной, с каверзными вопросами, странными задачами и вечным «а почему так, а не иначе?». В процессе я собрал целую коллекцию тем, которые всплывают снова и снова, особенно когда дело касается баз данных. Сегодня хочу поделиться этим опытом и разобрать вопросы, которые чаще всего задают именно по SQL-базам. В профиле уже есть другие части для подготовки: ❗❗Дисклеймер❗❗ Эта статья не является учебником по технологиям. Здесь я не буду углубляться в то, как всё работает под капотом или почему это устроено именно так. Это сжатая методичка по вопросам на собеседованиях — только факты, без лишней воды! Транзакции Транзакция — это набор операций с базой, который выполняется полностью или совсем не выполняется. ACID Атомарсность - никакая транзакция не будет зафиксирована в системе частично Согласованность - успешная транзакция всегда приводит данные в корректное, допустимое состояние Изолированность - параллельные транзакции не мешают друг другу и работают так, будто выполняются по очереди Прочность - если транзакция закоммитилась, её данные гарантированно сохранены и не исчезнут Уровни изоляции Read uncommitted Видит зафиксированные и не зафиксированные данные транзакций. PostgreSQL фактически не поддерживает “грязное чтение” (dirty read). Любые SELECT видят только зафиксированные данные, даже если указан Read Uncommitted. Это официально: в PostgreSQL Read Uncommitted ведёт себя как Read Committed. Read committed каждая операция внутри транзакции видит только те данные, которые были зафиксированы к моменту выполнения конкретного запроса, а не к началу транзакции Repeatable read В начале транзакции PostgreSQL делает снимок состояния БД (snapshot). Все последующие запросы этой транзакции читают одну и ту же консистентную картину, независимо от того, что успели закоммитить другие транзакции. Поэтому non-repeatable reads отсутствуют — одна и та же выборка всегда возвращает одинаковые данные в рамках транзакции. реализация Repeatable Read в PostgreSQL не допускает фантомных чтений Serializable Она гарантирует, что даже при конкурентном выполнении транзакций результат останется таким же, как и в случае их последовательного (по одной за раз) выполнения, без всякой конкурентности. Читающие транзакции не мешают друг другу — ты можешь спокойно читать данные, пока кто-то другой их меняет. Параллельные транзакции записи тоже не блокируют друг друга, если они работают с разными объектами. Блокировка возникает только тогда, когда две транзакции пытаются изменить один и тот же объект одновременно. Вот очень полезная табличка из официальной доки Postgres Индексы Индекс - это вспомогательная структура, которая хранится вместе с таблицей и помогает быстро находить нужные записи, ускоряя выборку данных. Важно отметить, что индексы могут также замедлять добавление, удаление и изменение данных, так как при этом операции также требуется обновление соответствующих индексов Хеш-индексы Хранилища типа ключ — значение работают как словари в языках программирования. Обычно реализуются через хеш-таблицу, где каждому ключу соответствует адрес значения в файле данных. При добавлении или обновлении пары «ключ — значение» хеш-таблица обновляется, чтобы хранить правильный адрес. А чтобы прочитать значение, система просто смотрит адрес в хеш-таблице и обращается к файлу — быстро и эффективно. SS-таблицы и LSM-деревья SS-таблица (SSTable) — это файл, где ключи отсортированы и каждый встречается только один раз. Такая структура облегчает слияние сегментов даже больших файлов, не загружая всю таблицу в память, и работает по принципу сортировки слиянием. Чтобы найти нужный ключ, не обязательно держать все индексы в памяти: достаточно знать соседние ключи и просматривать файл между ними до нужного значения. B-деревья B-дерево — самая распространённая индексная структура. Как и SSTable, оно хранит пары «ключ — значение» в отсортированном виде, что позволяет быстро искать ключи и выполнять диапазонные запросы. Но в отличие от SSTable, B-дерево делит данные на страницы фиксированного размера (обычно 4 КБ), которые читаются и записываются по одной. Каждая страница хранит ключи и ссылки на дочерние страницы, формируя дерево. Поиск всегда начинается с корневой страницы и проходит через страницы, отвечающие за диапазоны ключей, пока не найдётся нужное значение. Разница между индексами (user_id, status) и (user_id) INCLUDE (status) Составной индекс (user_id, status) 1. Структура: Оба поля являются частью ключа индекса 2. Сортировка: Данные сортируются сначала по user_id, затем по status 3. Использование: Эффективен для - o Запросов с условиями на user_id o Запросов с условиями на user_id И status o Сортировок по этим полям Индекс с INCLUDE (user_id) INCLUDE (status) 1. Структура: Только user_idв ключе индекса, status хранится в листьях индекса 2. Сортировка: Данные сортируются только по user_id 3. Использование: Эффективен для: o Запросов с условиями только на user_id o Запросов, где нужны оба поля (покрывающий индекс) o Не поддерживает поиск/сортировку по status отдельно Ключевые различия 1. Размер: INCLUDE-индекс обычно компактнее 2. Гибкость: Составной индекс поддерживает больше типов запросов 3. Производительность: Для запросов с фильтрацией по обоим полям составной индекс работает лучше 4. Поддержка: INCLUDE синтаксис доступен не во всех СУБД (появился в PostgreSQL 11, SQL Server и др.) Алгоритм вешанья индексов Это чистой мой алгоритм, я не говорю, что он САМЫЙ ВЕРНЫЙ и нет других) Алгоритм вешанья индексов 1) Определить цель индексирования: нужно понять, для каких операций или запросов индекс будет использоваться. Например, это может быть поиск по определенному полю, сортировка или слияние данных. 2) Оценить затраты и выгоды: провести анализ текущей структуры базы данных и определить, какие данные и операции будут выигрывать от индексирования. Оценить затраты на создание и поддержку индекса. 3) Анализ запросов: изучить типичные запросы, которые будут выполняться в базе данных, и выяснить, какие поля или комбинации полей чаще всего используются в условиях WHERE или ORDER BY. Это поможет определить, какие поля следует индексировать. 4) Избегать избыточности: создание индексов для каждого поля не всегда является эффективным решением. Определить комбинации полей, которые часто используются в запросах, и создать составные индексы для них. 5) Определить порядок сортировки: для поля, по которому будет выполняться сортировка или группировка, определить порядок и создать индекс соответствующего типа. 6) Определить уникальность: если поле должно содержать только уникальные значения, создать уникальный индекс для этого поля. 7) Обновление и поддержка индекса: учесть, что каждая операция добавления, обновления или удаления данных влияет на индексирование. Обновление индексов может занимать время и ресурсы, поэтому важно оптимизировать этот процесс. 8) Мониторинг и оптимизация: регулярно производить мониторинг производительности базы данных и запросов. Если производительность снижается, можно рассмотреть возможность создания или изменения индексов для улучшения выполнения запросов. Репликация Репликация — это копирование данных с одной базы на другие. Зачем: для отказоустойчивости и высокой доступности. Виды: синхронная (данные пишутся одновременно на все реплики) и асинхронная (реплики обновляются с задержкой). Реплика может использоваться для чтения, но запись идёт в мастер-базу. Шардирование Шардирование — это горизонтальное деление данных между разными серверами. Каждая шард содержит часть данных (например, по диапазону ключей или хешу). Позволяет масштабировать систему: нагрузка на запись и чтение распределяется между несколькими инстансами. Секционирование Секционирование — это деление таблицы внутри одной базы на логические части (секции). Например, по дате: январь, февраль, март. Упрощает поиск и может ускорять некоторые запросы, но все данные остаются в одной БД, в отличие от шардирования. SQL vs NoSQL SQL (реляционные БД) Структура: таблицы с фиксированными колонками и строками. Язык: SQL, строгие схемы (schema). Гарантии: ACID, транзакции. Когда применять: сложные связи между данными, строгие требования к консистентности, аналитика, банки, ERP. NoSQL (нереляционные БД) Структура: документы, ключ‑значение, графы или колоночные хранилища. Гибкие схемы, горизонтальное масштабирование проще. Когда применять: большие объёмы данных, высокая нагрузка на запись, быстрый поиск по ключу, распределённые системы, кэш, логи, IoT. Когда можно использовать денормализацию бд Денормализация — это сознательное объединение данных, которые обычно хранятся в разных таблицах, чтобы уменьшить количество соединений (JOIN) и ускорить запросы. Используют, когда: Часто выполняются сложные запросы с большим количеством JOIN, и это становится узким местом по производительности. Нужна оптимизация чтения в системах с высокой нагрузкой на SELECT. Можно пожертвовать скоростью записи ради ускорения чтения (так как обновление денормализованных данных сложнее). VIEW Это виртуальная таблица, которая хранит только запрос, а не данные. Данные берутся из базовых таблиц каждый раз при обращении. Плюс: удобство, безопасность, можно скрыть сложные JOIN’ы. Минус: запросы могут быть медленнее, если базовые таблицы большие. Materialized VIEW Это настоящая таблица, которая хранит результат запроса. Нельзя вставлять, обновлять или удалять строки напрямую — изменения происходят только через REFRESH MATERIALIZED VIEW .Плюс: быстрый доступ к данным, не нужно пересчитывать результат при каждом запросе. Минус: нужно периодически обновлять, чтобы данные оставались актуальными Итог Сегодня мы рассмотрели ключевые аспекты работы с базами данных: структуру таблиц, индексы, транзакции, запросы. Все это часто встречается на собеседованиях по SQL и работе с СУБД. Список тем составлен на основе моего опыта и опыта коллег, проходивших собеседования на позиции от Junior до Senior. Всем спасибо за внимание, удачных собесов и хорошего дня!)

Фильтры и сортировка