SQL

SQL #

1. Что такое транзакции? #

Транзакция - это набор логически связанных запросов, выполняемых атомарно (либо выполняются все - commit транзакции, либо откат всего - rollback)


2. Расскажите про ACID #

ACID - это набор свойств, гарантирующих надёжность транзакции:

  • Atomicity (атомарность/неделимость операций) - все операции внутри транзакции либо выполняются полностью, либо не выполняются совсем
  • Consistency (согласованность данных) - после транзакции данные соблюдают все правила целостности (правила целостности определяются бизнес-логикой)
  • Isolation (изоляция) - во время выполнения транзакции параллельные транзакции не должны оказывать влияние на ее результат
  • Durability (долговечность/живучесть) - гарантирует, что закоммиченные изменения не могут быть потеряны ни при каких сбоях (поломка сервера, отключение электроэнергии и т.д.)


3. Проблемы параллельных транзакций #

  • Грязное чтение (dirty read) - возможность чтения одной транзакцией незакоммиченных данных другой транзакции. Транзакция A обновляет значение в базе данных, но еще не закоммитила их. Транзакция B считывает это значение, не зная, что изменения могут быть отменены. Если транзакция A отменяется, значение, прочитанное транзакцией B, было “грязным”, так как оно не отражает окончательное состояние базы данных. Решается уровнем изоляции read committed и выше
  • Потерянное обновление (lost update) - при одновременном изменении одних и тех же данных разными транзакциями в силу вступают только изменения в последней транзакции. Решается repeatable read и выше
  • Неповторяющееся чтение (non-repeatable read) - возникает при изменении самих данных. Т.е. когда в рамках одной транзакции два чтения одних и тех же данных с промежутком времени дают два разных результата. Решается repeatable read и выше
  • Фантомное чтение (phantom read) - возникает при изменении объёма данных / строк. Т.е. во время одной транзакции другая транзакция добавила несколько строк в таблицу и закоммитила изменения. Во время повторного запроса данных первая транзакция получит больше данных (исходные + “фантомные” записи из другой транзакции)

В Postgres Repeatable Read реализован через MVCC настолько строго, что фантомное чтение там фактически не возникает (хотя стандарт SQL это допускает)

MVCC (Multiversion Concurrency Control) - это метод управления конкурентным доступом к данным в БД, который позволяет нескольким транзакциям работать с данными одновременно без конфликтов. MVCC поддерживает высокую производительность и изоляцию транзакций, минимизируя блокировки и улучшая параллелизм.

  • Многоверсионность: MVCC использует механизм многоверсионности для управления изменениями данных. Вместо того чтобы изменять данные в месте их хранения, MVCC создает новые версии данных. Каждая транзакция видит данные в том состоянии, в котором они были на момент начала транзакции, а не в текущем состоянии. При записи данных создается новая версия строки. Новая версия включает информацию о том, какая транзакция создала ее, и становится видимой для транзакций, которые начинаются после ее создания. Благодаря тому, что в версии указано, какая транзакция создала её и когда данные были созданы или удалены, можно определить, какая версия доступна для конкретной транзакции. Время от времени СУБД очищает старые версии данных, которые больше не нужны (т. е. которые уже не используются ни одной транзакцией). Это называется сбором мусора и помогает поддерживать эффективность системы.
  • Изоляция транзакций: Транзакции видят только те данные, которые были зафиксированы до их начала. Это предотвращает грязное чтение, неповторяющееся чтение и фантомное чтение.
  • Отсутствие блокировок для чтения: Чтение данных не блокирует запись, а запись не блокирует чтение. Это повышает производительность системы, поскольку транзакции могут работать с данными параллельно.
  • Управление конфликтами: Конфликты между транзакциями, например, две транзакции, пытающиеся изменить одну и ту же строку, решаются при коммите транзакций. Если одна из транзакций не может быть закоммичена из‑за конфликта, её можно откатить.


4. Уровни изолированности (read uncommitted, read committed, repeatable read, serializable) #

Статья на хабре про уровни изоляции

От менее строгого к самому строгому:

  • Read uncommited (незащищённое чтение) - транзакции могут читать данные, которые еще не зафиксированы другими транзакциями (использовать, когда все транзакции на чтение)
  • Read commited (защищённое чтение) - транзакции могут читать только те данные, которые уже зафиксированы другими данными (решает потерянное обновление и грязное чтение)
  • Repeatable read (повторяющееся чтение) - в рамках одной транзакции многократный запрос данных всегда возвращает одинаковый набор данных, независимо от изменений, сделанных другими транзакциями (решает потерянное обновление, грязное чтение и неповторяющееся чтение)
  • Serializable - самый строгий уровень изоляции. Транзакции выполняются так, как будто происходят последовательно одна за другой (решает всё, но бьёт по производительности)

Какой уровень изолированности стоит по умолчанию в MySQL? В MySQL - Repeatable read, в PostgreSQL - Read commited

Зависимость скорости работы БД и уровня изоляции

Уровень изоляцииОписаниеВлияние на производительность
Read UncommittedТранзакции могут читать данные, которые ещё не зафиксированыСамый быстрый, но данные могут быть неконсистентными
Read CommittedТранзакции читают только зафиксированные данныеСредняя производительность, баланс между скоростью и консистентностью
Repeatable ReadГарантирует, что данные, прочитанные транзакцией, не изменятся, пока транзакция не завершенаСнижает производительность из-за блокировок, но обеспечивает более высокую консистентность
SerializableСамый высокий уровень изоляции, предотвращает все виды аномалийСамый медленный, так как использует полные блокировки


5. Виды JOIN. Как работает каждый JOIN? #

Оператор, позволяющий связать данные из двух таблиц. Бывают:

  • (INNER) JOIN - выдаст общее для левой и правой таблицы (может быть заменён WHERE)
  • LEFT (OUTER) JOIN - выдаст все значения с левой таблицы и все подходящие с правой
  • RIGHT (OUTER) JOIN - выдаст все значения с правой таблицы и все подходящие с левой
  • FULL (OUTER) JOIN - выдаст все записи, которые присутствуют в таблицах
  • CROSS JOIN - создаст декартово произведение двух таблиц (если в первой таблице 5 строк, а во второй 3, то вернет 5 x 3 = 15)
  • NATURAL JOIN - объединяет таблицы по столбцам с одинаковыми именами, если значения в этих столбцах совпадают
  • SELF JOIN - соединяет таблицу с самой собой


6. Индексы в SQL. Что это? #

Индексы в БД - это инструмент оптимизации/ускорения запросов путём создания упорядоченного набора указателей на строки таблицы. Ускорение происходит за счёт того, что структура индекса оптимизирована под поиск (например, b-tree). При этом, чем больше дубликатов в столбце - тем хуже работает индекс

В индексе ссылка на данные или в самом индексе кешируем данные? В индексе хранятся ссылки на реальные данные, которые находятся в таблице базы данных. Эти ссылки могут быть представлены в виде:

  • указателей на строки в таблице, где хранятся данные
  • идентификаторов записей, которые позволяют найти данные в таблице

В Postgres в индексе хранятся ссылки на реальные данные, которые находятся в таблице базы данных. Например, в B-tree:

  • промежуточные узлы дерева содержат только ключи для направления поиска
  • в листьях дерева хранятся не сами данные, а ссылки на данные в таблице (или другие индексы)

Однако, можно создать покрывающий индекс - индекс, который включает не только ключи, но и значение колонки(ок). Такие индексы позволяют извлекать данные не обращаясь к таблице. Пример:

CREATE INDEX idx_price ON products(price) INCLUDE (name);
-- Теперь индекс хранит значения price + колонку name, что позволяет выполнить запрос
SELECT price, name FROM products WHERE price > 50;
-- не обращаясь к таблице

В MySQL (InnoDB) поведение индексов отличается в зависимости от их типа:

  1. PRIMARY KEY (кластерный индекс) - индекс содержит сами данные
  2. Обычный (SECONDARY INDEX) индекс хранит ссылки (ключи PRIMARY KEY) на данные

Какие индексы по умолчанию в разных СУБД? По-умолчанию в большинстве CУБД стоит B-tree

Сравнение сложности поиска

Тип поискаСтруктураСложностьКогда применять?
Без индексаПолный переборO(N)Маленькие таблицы
B-Tree индексСбалансированное деревоO(log N)Частый поиск по диапазонам
Hash-индексХеш-таблицаO(1)Быстрый поиск по =

Как добавить новый индекс в большую, активно используемую таблицу в продакшене, не блокируя доступ и не приостанавливая работу приложения, и какие стратегии можно применить для минимизации влияния на систему?

  • PostgreSQLCREATE INDEX CONCURRENTLY / pg_repack / gh-ost
  • MySQLALTER TABLE … ALGORITHM=INPLACE, LOCK=NONE / pt-online-schema-change

Как определить, какой индекс будет использован в запросе с фильтрацией по столбцу с низкой селективностью (например, пол), и как селективность влияет на эффективность индексов?

  1. EXPLAIN ANALYZE покажет, будет ли Index Scan или Seq Scan
  2. pg_stats.n_distinct даёт оценку числа уникальных значений


7. Почему когда много индексов, это плохо? #

  • Усложнение работы планировщика - СУБД может не выбрать лучший индекс для запроса, если их слишком много. Некоторые индексы могут быть избыточными, что не только не ускоряет запросы, но и увеличивает нагрузку на систему
  • Утилизация места на жёстком диске - индексы занимают дополнительное место на диске пропорционально объёму данных и кол-ву выбранных столбцов
  • Увеличение времени записи - при вставке, обновлении или удалении данных индексы нужно обновлять, что замедляет операции записи
  • Усложнение управления - управление большим количеством индексов становится сложным, включая их отслеживание и обслуживании


8. Плюсы и минусы индексов #

Плюсы:

  • Улучшают производительность для select и сортировки по определенным полям

Минусы:

  • Ухудшается производительность, когда нужно вставлять, обновлять или удалять данные
  • Требуется дополнительное место и чем больше/длиннее ключ - тем больше размер индекса


9. Какие виды индексов знаешь? #

Тип индексаОписаниеИспользование
B-дерево (B-tree)Стандартный индекс, используемый в большинстве СУБД. Данные хранятся в сбалансированном деревеДля поиска, сортировки, диапазонных запросов
Хеш-индекс (Hash Index)Использует хеш-функцию для поиска точных совпаденийДля быстрого поиска по уникальным значениям (например, ID)
Индекс с несколькими столбцами (Composite Index)Индекс, включающий несколько столбцов. Ускоряет запросы, использующие несколько фильтровКогда запросы фильтруют по нескольким столбцам одновременно
Индекс с уникальными значениями (Unique Index)Индекс, обеспечивающий уникальность значений в столбцеДля обеспечения уникальности значений (например, email)
Индекс полного текста (Full-Text Index)Индекс для быстрого поиска по тексту, включая ключевые слова и фразыПоиск по длинным текстовым полям, например, в статьях или документах
Индекс обратного списка (Bitmap Index)Индекс с битовыми картами для колонок с малым числом уникальных значенийДля столбцов с ограниченным числом уникальных значений, например, пол
Инкрементный (Clustered Index)Индекс, при котором данные таблицы физически упорядочены в соответствии с индексомКогда необходимо хранить данные в порядке индекса, например, по ID
Индекс по выражению (Functional Index)Индекс, основанный на вычисленных значениях или функциях над столбцамиДля запросов, использующих функции или выражения в фильтрах
Индекс на основе дерева (GiST — Generalized Search Tree)Общий тип дерева, используемый для индексации сложных типов данныхДля индексации географических данных или других сложных типов
Индекс на основе префикса (Prefix Index)Индекс, созданный на части строки, например, на первых символахДля текстовых полей, где важно индексировать только часть строки (например, первые несколько символов)
GIN (Generalized Inverted Index)Инвертированная структура: GIN создает карту, связывающую значения (ключи) с идентификаторами строк (TID), в которых эти значения встречаются. B-tree под капотомИдеален дляJSONB (операторы @>, ?), ARRAY (операторы <@, @>, &&)
Покрывающий индекс (Covering Index)Схож с композитным, но INCLUDE-колонки не участвуют в поиске/фильтрацииCREATE INDEX idx_users_coverON users(email)INCLUDE(name, age)Для определённого набора часто запрашиваемых полей, не участвующих в фильтрации


10. Как правильно выбрать индекс? #

Тип индексаКогда использоватьГде доступен
B-TreeУниверсальный, для =<>ORDER BYВезде
HashТолько = (поиск по точному значению)PostgreSQL, MySQL MEMORY
BitmapКолонка с малым числом уникальных значений (status)Oracle, Postgres ext
Full-text/GIN/GiSTПоиск по тексту, JSON, массивам, гео-даннымPostgreSQL, MySQL
ClusteredPrimary key, диапазоныSQL Server, MySQL InnoDB
Non-clusteredДополнительные индексыSQL Server, MySQL
PartialИндекс только по условию WHEREPostgreSQL, SQL Server


11. Нормализация и денормализация. Перечислите формы #

Нормализация - это процесс преобразования отношений базы данных к виду без избыточной информации. Избыточность - это ситуация, когда одни и те же данные хранятся в базе в нескольких местах. Нормализация проходит через несколько форм:

  • 1NF (1-я нормальная форма). Одна ячейка - одно значение.
  • 2NF (2-я нормальная форма). Все не ключевые колонки зависят от всего ключа.
  • 3NF (3-я нормальная форма). Все не ключевые колонки не зависят друг от друга.

Денормализация - процесс обратный нормализации. Обычно это необходимо для повышения производительности и скорости извлечения данных, за счет увеличения избыточности данных (влечёт уменьшение сложности запросов)


12. Что такое внешний ключ? #

Внешний ключ - это столбец-ссылка на первичный ключ (или уникальную колонку) другой таблицы. Может быть null.


13. Чем WHERE отличается от HAVING? Можем ли мы их использовать в одном запросе? #

WHERE отвечает за фильтр всех записей в запросе (до применения агрегатных функций), HAVING применяет фильтры к группам, сформированным GROUP BY


14. Чем отличается MySQL от PostgreSQL? #

MySQL - проще, быстрее в базовых операциях, хорош для приложений с высокой нагрузкой на чтение PostgreSQL - мощнее, с расширенными функциями и более строгим соблюдением стандартов, лучше подходит для сложных приложений


15. Оптимизация запросов. Какие существуют инструменты? #

  • EXPLAIN - показывает теоретический план выполнения запроса, что позволяет увидеть, как СУБД планирует извлекать данные
  • EXPLAIN ANALYZE - выполняет запрос и показывает реальный план выполнения, включая затраченные ресурсы (время, память)
  • Indexes - индексы ускоряют доступ к данным, особенно при использовании в условиях WHERE
  • pg_stat_statements (для PostgreSQL) - модуль для сбора статистики выполнения запросов

SQL-запрос у тебя в норме, но выполнение всё равно долгое. Твои действия?

  • Оптимизация плана выполнения - воспользоваться EXPLAIN и EXPLAIN ANALYZE для анализа плана выполнения запроса и поиска проблемных мест
  • Использование индексов - проверить, правильно ли используются существующие индексы. При необходимости создать или изменить текущие
  • Рефакторинг запроса - изменить структуру запроса, если есть более эффективный способ получить те же данные. Например, объединение нескольких подзапросов или использование более простых JOIN
  • Ресурсные ограничения - проверить отсутствие ограничений по железу (нехватка CPU, памяти, диска)


16. EXPLAIN vs EXPLAIN ANALYZE #

EXPLAIN показывает план выполнения запроса до его фактического выполнения. Он выводит, как СУБД собирается получить данные. Это включает:

  • какой тип сканирования будет применяться (Seq Scan, Index Scan)
  • сортировки и объединения (Nested Loop, Hash Join)
  • предполагаемое количество строк и стоимость выполнения на каждом этапе

Explain analyze. Как устроен? На что обращать внимание? EXPLAIN ANALYZE не только показывает план выполнения, но и выполняет запрос, выводя фактическое время выполнения каждой операции. Полезные моменты:

  • фактическое время (Actual Time) - сколько времени заняла операция
  • ROWS - реальное количество строк, прошедших через каждый этап
  • LOOPS - количество раз, когда этот шаг был выполнен (важно для вложенных циклов)
EXPLAIN ANALYZE SELECT * FROM employees WHERE salary > 100000;


17. Инструмент для сбора статистики в PostgreSQL #

В PostgreSQL используется встроенный модуль pg_stat_statements для сбора статистики по запросам. Сохраняет информацию о запросах, частоте и времени их выполнения, а также сколько ресурсов потребляют


18. Что такое селективность? #

Селективность - это характеристика избирательности запроса. Если значения в колонке варьируются в небольшом промежутке, то селективность запроса, выбирающего значения по этой колонке - низкая. Высокая селективность позволяет сильно ускорить процесс вычитывания данных


19. Ключевое слово LATERAL #

LATERAL позволяет использовать данные из каждой строки внешней таблицы в подзапросе (аналог “для каждой строки”)

Пример: Допустим, у нас есть таблица users и мы хотим для каждого пользователя взять его последние 3 транзакции из таблицы transactions

SELECT u.id, u.name, t.*
FROM users u,
LATERAL (
  SELECT *
  FROM transactions t
  WHERE t.user_id = u.id
  ORDER BY t.date DESC
  LIMIT 3
) t;
  • Для каждой строки таблицы users выполняется подзапрос с ограничением (LIMIT 3)
  • Без LATERAL подзапрос не мог бы ссылаться на внешнюю таблицу u


20. Оконные функции. PARTITION BY #

Оконные функции позволяют делить выборку на непересекающиеся группы (партиции) по какому-либо столбцу и работать с ними независимо

Синтаксис:

SELECT <оконная_функция>(<поле_таблицы>)
OVER (
      [PARTITION BY <столбцы_для_разделения>]
      [ORDER BY <столбцы_для_сортировки>]
      [ROWS|RANGE <определение_диапазона_строк>]
)
  • PARTITION BY <столбцы_для_разделения> делит выборку на непересекающиеся подмножества, где каждое подмножество содержит строки с одинаковыми значениями в одном или нескольких столбцах, образуются партиции. Аналог GROUP BY, но не агрегирует строки, а позволяет работать с ними индивидуально в пределах группы
  • ORDER BY <столбцы_для_сортировки> устанавливает порядок строк внутри окна, особо важную роль играет в оконных функциях ранжирования
  • ROWS|RANGE <определение_диапазона_строк> формирует диапазоны строк. С помощью этого параметра можно указать сколько строк брать до и после текущей в окно

Синтаксиc ROW | RANGE

ROWS|RANGE BETWEEN <начало границы окна> AND <конец границы окна>
  • UNBOUNDED PRECEDING — все строки, предшествующие текущей
  • N PRECEDING - N строк до текущей строки
  • CURRENT ROW - текущая строка
  • N FOLLOWING - N строк после текущей строки
  • UNBOUNDED FOLLOWING - все последующие строки

Пример: Пусть есть таблица sales с колонками regionsalesperson и sales. Мы хотим вычислить сумму продаж в каждом регионе и ранжировать сотрудников внутри регионов

SELECT
  region,
  salesperson,
  sales,
  SUM(sales) OVER (PARTITION BY region) AS total_region_sales,
  RANK() OVER (PARTITION BY region ORDER BY sales DESC) AS rank_in_region
FROM sales;
  • PARTITION BY region - группировка по регионам
  • SUM(sales) - выполняется для каждой строки, но только в рамках региона
  • RANK() - присваивает ранг на основе продаж внутри каждого региона


21. cube, rollup, groupBy #

CUBE Создаёт все возможные комбинации группировок.

SELECT region, product, SUM(sales)
FROM sales
GROUP BY CUBE(region, product);

Результат:

  • Группировка по region, product
  • Группировка только по region
  • Группировка только по product
  • Общая сумма по всем продажам

ROLLUP Создаёт иерархические уровни группировки (сверху вниз)

SELECT region, product, SUM(sales)
FROM sales
GROUP BY ROLLUP(region, product);

Результат:

  • Группировка по region, product
  • Группировка только по region
  • Общая сумма по всем продажам

GROUPING SETS Явно указывает на комбинации группировки

SELECT region, product, SUM(sales)
FROM sales
GROUP BY GROUPING SETS ((region, product), (region), ());

Результат идентичен ROLLUP, но группы можно задать произвольно

GROUP BY используется для группировки строк по определённым столбцам и применения агрегатных функций к каждой группе. Это позволяет выполнять расчёты по группам данных, например, подсчёт суммы, среднего значения и т.д.

Пример: Есть таблица sales с колонками region и revenue. Чтобы найти суммарный доход по каждому региону:

SELECT region, SUM(revenue) AS total_revenue
FROM sales
GROUP BY region;
  • region - группирующий столбец
  • SUM(revenue) вычисляется для каждой группы

Если не использовать GROUP BY, SQL будет рассматривать все строки как одну группу


22. Хранимые процедуры #

Хранимая процедура (по сути аналог функции в Java) - это единожды созданный и сохранённый в базе данных набор SQL-инструкций (PL/pgSQL, PL/Python), выполняющий определённую задачу. Служит для повторного использования кода и оптимизации

Ключевые особенности:

  • Входные параметры - передача данных в процедуру
  • Выходные параметры - возврат результата
  • Операторы управления потоком исполнения - циклы, условные конструкции (IFWHILE)

Пример создания и вызова хранимой процедуры:

CREATE PROCEDURE CalculateBonus (IN employee_id INT, OUT bonus_amount DECIMAL(10,2))
BEGIN
  SELECT salary * 0.1 INTO bonus_amount
  FROM employees
  WHERE id = employee_id;
END;

Вызов:

CALL CalculateBonus(101, @bonus);
SELECT @bonus;

Преимущества:

  • снижение трафика между клиентом и сервером
  • лучшая организация кода


23. Как работает Serializable? #

Serializable (последовательный) - самый строгий уровень изоляции транзакций. Обеспечивает такое выполнение транзакций, как если бы они происходили последовательно (по одной за раз), даже если фактически выполняются параллельно. Использует блокировки или механизм версионирования (MVCC) для управления конкурентным доступом Полностью предотвращает все 4 типа транзакционных проблем

Может приводить к снижению производительности из-за увеличения времени ожидания между транзакциями

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;

-- Первая транзакция
SELECT SUM(balance) FROM accounts WHERE account_type = 'savings';

-- Вторая транзакция (ожидание завершения первой)
INSERT INTO accounts (account_type, balance) VALUES ('savings', 500);

COMMIT;


24. View, materialized view. Разница между обычным и материализованным представлением? #

View (представление) - это сохранённый SQL-запрос, который служит для удобного (без необходимости повторно писать запрос) получения определённой выборки данных. Не хранит данные

CREATE VIEW ActiveUsers AS 
SELECT id, name, email 
FROM users 
 WHERE status = 'active'; 
 
 -- Использование представления 
 SELECT * FROM ActiveUsers;

Materialized View (материализованное представление) - это непосредственно выборка, полученная по какому-то запросу и сохранённая в памяти. Может ускорить выполнение сложных запросов за счёт того, что не перечитывает данные. Не актуализируется автоматически (требует REFRESH), а потому данные могут быть не консистентными

CREATE MATERIALIZED VIEW SalesSummary AS
SELECT region, SUM(revenue) AS total_revenue
FROM sales
GROUP BY region;

-- Обновление данных
REFRESH MATERIALIZED VIEW SalesSummary;


25. C помощью SQL блокировка строки? - SELECT FOR UPDATE #

SELECT FOR UPDATE используется для блокировки строк в таблице, участвующих в запросе до окончания транзакции, чтобы предотвратить их изменение другими транзакциями

BEGIN;

-- Блокировка строки с id = 1
SELECT * FROM accounts
WHERE id = 1
FOR UPDATE;

-- Делаем обновление
UPDATE accounts
SET balance = balance - 100
WHERE id = 1;

COMMIT;
  • В данном случае, строка с id = 1 заблокирована для других транзакций, пока текущая не завершится
  • Если другая транзакция попытается заблокировать ту же строку, она будет ожидать завершения текущей транзакции


26. UNION vs UNION ALL #

UNION и UNION ALL объединяют результаты двух или более запросов. Разница в том, что UNION осуществляет поиск и исключение дублирующихся строк, а UNION ALL - нет. В следствие этого UNION может работать медленнее. Оба оператора требуют, чтобы количество и типы столбцов в объединяемых выборках совпадали

SELECT city FROM customers
UNION [ALL]
SELECT city FROM suppliers;

Если оба запроса возвращают city = 'New York', то в результате UNION эта строка появится только один раз. В случае UNION ALL - дважды


27. Расскажи про ключевое слово DISTINCT? #

DISTINCT - это ключевое слово SQL, служащее для выбора только уникальных записей, т.е. устраняет дублирующиеся строки из результата запроса


28. Что такое табличные выражения? #

Обобщённое табличное выражение или CTE (Common Table Expressions) - это именованный временный результирующий набор данных, к которому можно обращаться в последующих запросах. Реализовано через ключевое слово WITH

WITH recent_sales AS (
    SELECT * 
    FROM sales 
    WHERE sale_date > '2023-01-01'
)
SELECT * FROM recent_sales WHERE amount > 1000

Временная таблица recent_sales доступна только внутри текущего запроса

CTE могут быть материализованными и нематериализованными. По умолчанию это решает планировщик на основании анализа использования CTE в последующих запросах. Но можно явно указать этот параметр

WITH recent_sales AS [NOT] MATERIALIZED (
    SELECT * 
    FROM sales 
    WHERE sale_date > '2023-01-01'
)


29. Для чего используется Database Connection Pool? (Hikari) Исчерпание connection pool #

Database Connection Pool - это пул подключений к базе данных, который управляет ограниченным числом соединений, чтобы уменьшить нагрузку на сервер базы данных и повысить производительность. Hikari - один из самых популярных Connection Pool

Почему нужен пул?

  • Уменьшает накладные расходы на создание/закрытие соединений
  • Повышает производительность многопоточных приложений

Так как открытие и закрытие подключений к базе данных - дорогостоящая операция, то выгоднее держать пул из открытых подключений, и выдавать их по запросу. Долго простаивающие подключения закрываются.

Более того, ограничение кол-ва подключений в пуле позволяет исключить перегрузку базы данных множественными подключениями. При отсутствии свободных подключений желающий обратиться к БД обязан ждать освобождения одного из подключений пула.

Исчерпание Connection Pool происходит, когда приложению требуется больше соединений, чем разрешено пулом. Возможные причины:

  • утечка соединений (не закрываются после использования)
  • неправильная настройка maxPoolSize
  • избыточная нагрузка на приложение

Решения:

  1. Закрывать соединения через try-with-resources
  2. Увеличить maxPoolSize, если это оправданно


30. Блокировки и локи. Оптимистичная и Пессимистичная #

Блокировки (или локи) в базе данных - это механизм, управляющий конкурентным доступом к данным, чтобы избежать конфликтов и ошибок. Есть два типа блокировок

Оптимистическая блокировка - при завершении транзакции сверяется текущая версия данных и версия, полученная в начале транзакции. Если они не совпадают, то транзакция откатывается. Подразумевает, что конфликты будут происходить нечасто

SELECT version 
FROM orders 
WHERE id = 1;

-- Обновляем, если версия не изменилась
UPDATE orders
SET status = 'processed', version = version + 1
WHERE id = 1 AND version = 5;

Пессимистическая блокировка - при начале транзакции изменяемые данные блокируются для всех остальных, пока текущая транзакция не закончится

SELECT *
FROM orders
WHERE id = 1 
FOR UPDATE;

Сравнение

ХарактеристикаОптимистичные локиПессимистичные локи
КонфликтыРедкиеЧастые
МеханизмПроверка версии данныхБлокировка ресурса
ПроизводительностьВыше при редких конфликтахНиже из-за блокировок
Обработка конфликтовТребует доп. кодаНе требуется
ПрименимостьЧтение превалирует над записьюВысококонкурентная запись


31. БД. Constraint. Какие существуют? #

Constraint (ограничение) — это правило, накладываемое на столбцы или таблицы, чтобы обеспечить целостность и корректность данных

ОграничениеОписаниеПрименимость
NOT NULLПоле обязательно для заполненияИдентификаторы, ключевые данные
UNIQUEЗначения в поле уникальныEmail, username
PRIMARY KEYУникально идентифицирует строкуОсновной идентификатор записи
FOREIGN KEYОбеспечивает целостность связи между таблицамиТаблицы с зависимыми данными
CHECKУстанавливает условия для значенийБизнес-правила
DEFAULTЗначение по умолчаниюДаты, статусы
EXCLUSION (для постгрес)Обобщённый случай уникальности, позволяет писать кастомные условия для сравнения двух столбцов, которые должны соблюдаться при добавлении

Пример EXCLUDE

CREATE TABLE reservations  
(  
	id serial PRIMARY KEY,  
	room_id INTEGER,  
	booking_status TEXT,  
	start_date TIMESTAMP,  
	end_date TIMESTAMP,  
	EXCLUDE USING GIST (TSRANGE(start_date, end_date) WITH &&)  
);

The EXCLUDE USING GISTclause creates an exclusion constraint that ensures there are no conflicting bookings for the same date range. The TSRANGE(start_date, end_date) WITH && condition checks for overlapping date ranges.


32. Primary Key #

Primary Key (PK) - это уникальный идентификатор записи в таблице. Без него сложно эффективно работать с данными, особенно в больших таблицах

  1. Гарантия уникальности - обеспечивает, что ни одна запись не будет полностью дублироваться, а значит может быть безошибочно идентифицирована
  2. Связь между таблицами - используется как основа для создания внешних ключей (FK)
  3. Ускорение поиска - PK автоматически индексируется, что ускоряет выборку данных

PK индексирован? Да, Primary Key автоматически индексируется базой данных. Этот индекс включает UNIQUE и NOT NULL ограничения

  • Ускоряет поиск, сортировку и фильтрацию по PK
  • Гарантирует уникальность значений в столбце PK


33. Foreign Key #

Foreign Key (внешний ключ) - это колонка (или набор колонок) таблице, ссылающаяся на первичный ключ (или любую UNIQUE колонку) другой таблицы. Обеспечивает ссылочную целостность.

CREATE TABLE users (
    id INT PRIMARY KEY
);

CREATE TABLE orders (
    order_id INT PRIMARY KEY,
    user_id INT,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

Foreign Key может быть составным? Да, Foreign Key может быть составным (Composite Foreign Key)

ХарактеристикаPrimary Key (PK)Foreign Key (FK)
НазначениеГарантирует уникальность строкОбеспечивает ссылочную целостность
УникальностьВсегда уникаленМожет повторяться
NULL?НельзяМожно
КоличествоОдин на таблицу (может быть составным)Может быть несколько
УдалениеНельзя менять без CASCADEON DELETE CASCADE - удалит зависимые записи


34. DELETE vs TRUNCATE #

  • DELETE - оператор DML, удаляет записи из таблицы, которые удовлетворяют условиям WHERE. Ведёт журналирование. Не сбрасывает последовательности. Медленнее. Есть возможность восстановить данные

  • TRUNCATE - DDL оператор, удаляет все строки из таблицы, быстро высвобождая занятую память. Не ведёт журналирование, не сбрасывает последовательности. Нет возможности восстановить данные