Вопрос 1. В чём разница между INNER JOIN, LEFT JOIN и FULL OUTER JOIN? Приведите пример.
Ответ:
- INNER JOIN — возвращает только строки, присутствующие в обеих таблицах.
- LEFT JOIN — возвращает все строки из левой таблицы, даже если нет совпадений справа (правые поля = NULL).
- FULL OUTER JOIN — возвращает все строки из обеих таблиц, заполняя NULL там, где нет совпадений.
-- Пример: пользователи и заказы
SELECT u.name, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
-- Покажет всех пользователей, даже без заказов
Практика в Java: Используйте LEFT JOIN FETCH в JPQL, чтобы избежать N+1 при загрузке связей.
🔹 Вопрос 2. Что такое индекс? Как он работает? Когда его стоит создавать?
Ответ:
Индекс — это структура данных (обычно B-дерево), которая ускоряет поиск по столбцу(ам), подобно оглавлению в книге.
- Ускоряет: WHERE, JOIN, ORDER BY.
- Замедляет: INSERT, UPDATE, DELETE (индекс тоже нужно обновлять).
Создавайте индекс, если:
- Колонка часто используется в WHERE,
- Выборка составляет < 10–15% от таблицы,
- Это внешний ключ (user_id в orders).
⚠️ Не создавайте индексы на маленьких таблицах — полный скан быстрее.
🔹 Вопрос 3. Что такое EXPLAIN и как его использовать для оптимизации запросов?
Ответ:
EXPLAIN показывает план выполнения запроса — какие индексы используются, сколько строк просканировано, есть ли Seq Scan (полный перебор).
EXPLAIN ANALYZE
SELECT * FROM orders WHERE user_id = 123;
Ищите:
- Index Scan → хорошо,
- Seq Scan на большой таблице → плохо,
- высокое значение rows → возможно, не хватает индекса.
💡 В Java: Логируйте медленные запросы через spring.jpa.properties.hibernate.generate_statistics=true.
🔹 Вопрос 4. Что такое N+1 проблема? Как её решить на уровне SQL и Hibernate?
Ответ:
N+1 — когда вы делаете 1 запрос для получения N записей, а затем N запросов для каждой связи.
// Плохо: вызовет N+1
List<User> users = userRepository.findAll();
for (User u : users) {
System.out.println(u.getOrders().size()); // lazy load → N запросов
}
Решение в SQL:
SELECT u.*, o.*
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE ...;
В Hibernate:
- JOIN FETCH в JPQL,
- @EntityGraph,
- FetchMode.JOIN.
✅ Правило: всегда проверяйте количество SQL-запросов в логах!
В вопрос 5. Что такое транзакция? Какие свойства ACID вы знаете?
Ответ:
Транзакция — это логическая единица работы, которая либо выполняется полностью, либо не выполняется вовсе.
ACID:
- Atomicity — всё или ничего.
- Consistency — данные переходят из одного согласованного состояния в другое.
- Isolation — транзакции не мешают друг другу.
- Durability — после коммита изменения сохраняются даже при сбое.
💡 В Spring: @Transactional управляет этими свойствами.
🔹 Вопрос 6. Какие уровни изоляции транзакций вы знаете? Чем грозит READ COMMITTED vs SERIALIZABLE?
Ответ:
Уровень
Проблемы
Производительность
READ UNCOMMITTED
Dirty Read
Самая высокая
READ COMMITTED (по умолчанию в PostgreSQL)
Non-repeatable Read
Хорошая
REPEATABLE READ
Phantom Read
Средняя
SERIALIZABLE
Нет проблем
Самая низкая
Dirty Read — чтение незафиксированных данных.
Non-repeatable Read — повторное чтение даёт другой результат.
Phantom Read — новые строки появляются при повторном запросе.
⚠️ В Java: Указывайте уровень явно:
@Transactional(isolation = Isolation.REPEATABLE_READ)
🔹 Вопрос 7. Что такое deadlock? Как его избежать?
Ответ:
Deadlock — циклическое ожидание:
Транзакция A ждёт ресурс у B, B — у A.
Как избежать:
- Блокировать ресурсы в одинаковом порядке,
- Использовать таймауты (lock_timeout),
- Делать транзакции короткими.
💡 В PostgreSQL: автоматически выбирается жертва (deadlock detected), одна транзакция откатывается.
🔹 Вопрос 8. В чём разница между WHERE и HAVING?
Ответ:
- WHERE фильтрует до группировки.
- HAVING фильтрует после GROUP BY.
SELECT department, AVG(salary)
FROM employees
WHERE salary > 50000 -- до группировки
GROUP BY department
HAVING AVG(salary) > 70000; -- после группировки
✅ Запомните: HAVING работает только с агрегатными функциями (COUNT, SUM, AVG).
🔹 Вопрос 9. Что такое CTE (Common Table Expression)? Зачем он нужен?
Ответ:
CTE — временный именованный результат, который можно использовать в запросе.
WITH high_salary AS (
SELECT * FROM employees WHERE salary > 100000
)
SELECT name, department
FROM high_salary
WHERE department = 'Engineering';
Преимущества:
- Улучшает читаемость,
- Можно рекурсивно вызывать (для иерархий),
- Оптимизатор может лучше обработать запрос.
💡 В Java: Используйте CTE в native-запросах через @Query.
🔹 Вопрос 10. Как реализовать пагинацию в SQL? Какие есть проблемы?
Ответ:
OFFSET/LIMIT (PostgreSQL, MySQL):
SELECT * FROM orders ORDER BY id LIMIT 20 OFFSET 40;
роблемы:
- OFFSET медленный на больших значениях (СУБД пропускает N строк),
- Может пропустить/продублировать записи при изменении данных.
Решение: Keyset pagination (курсорная пагинация):
SELECT * FROM orders
WHERE id > 1000
ORDER BY id
LIMIT 20;
✅ В Spring Data JPA: Pageable использует OFFSET, но для больших данных — пишите кастомный запрос.
🔹 Вопрос 11. Что такое оконные функции (Window Functions)? Приведите пример.
Ответ:
Оконные функции выполняют расчёт по группе строк, не сворачивая их (в отличие от GROUP BY).
SELECT
name,
salary,
AVG(salary) OVER (PARTITION BY department) AS dept_avg
FROM employees;
Популярные функции: ROW_NUMBER(), RANK(), LEAD(), LAG().
💡 Использование: Топ-N в группе, скользящее среднее, сравнение с предыдущим значением.
🔹 Вопрос 12. Что такое нормализация и денормализация? Когда что применять?
Ответ:
- Нормализация — устранение дублирования данных (до 3NF). Уменьшает аномалии, но требует JOIN’ов.
- Денормализация — намеренное дублирование для ускорения чтения.
Применяйте денормализацию, если:
- Чтение >> записи,
- JOIN’ы слишком дорогие,
- Вы готовы к усложнению логики обновления.
⚠️ В микросервисах: Денормализация — норма (каждый сервис имеет свою БД).
🔹 Вопрос 13. Как найти дубликаты в таблице?
Ответ:
SELECT email, COUNT(*)
FROM users
GROUP BY email
HAVING COUNT(*) > 1;
Удаление дубликатов (оставить минимальный id):
DELETE FROM users
WHERE id NOT IN (
SELECT MIN(id)
FROM users
GROUP BY email
);
💡 В Java: Всегда добавляйте UNIQUE-ограничение на уровне БД!
🔹 Вопрос 14. Что такое UPSERT? Как его реализовать в PostgreSQL и MySQL?
Ответ:
UPSERT = UPDATE + INSERT — вставить, если нет, обновить, если есть.
PostgreSQL:
INSERT INTO users (id, name) VALUES (1, 'Alice')
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name;
✅ В Spring Data JPA: Используйте save() — он делает merge, но для true upsert — native query.
🔹 Вопрос 15. Как обеспечить целостность данных на уровне приложения (Java) и БД?
Ответ:
На уровне БД (надёжнее!):
- PRIMARY KEY, FOREIGN KEY,
- UNIQUE, CHECK constraints,
- NOT NULL.
На уровне Java:
- Валидация через javax.validation (@NotNull, @Email),
- Транзакции через @Transactional,
- DTO для защиты от overposting.
🔥 Главное правило: Никогда не полагайтесь только на валидацию в коде!
Всегда дублируйте критические ограничения в БД.