added files theory bd with indexes and partitions
This commit is contained in:
@@ -0,0 +1,202 @@
|
|||||||
|
- Задание 1: По каким полям делать индексы
|
||||||
|
|
||||||
|
Возьми таблицы из пет проекта “Онлайн магазин”.
|
||||||
|
|
||||||
|
**Ответь:**
|
||||||
|
|
||||||
|
1. По каким полям, как тебе кажется, реальный продукт‑менеджер или аналитик будет чаще всего:
|
||||||
|
- искать (`WHERE`),
|
||||||
|
по странам пользователя, по категориям техники, стомиость, возраст пользователей, статус заказа, активность пользователей.
|
||||||
|
- сортировать (`ORDER BY`),
|
||||||
|
количество заказов, стоимость, возраст пользователей, дата заказа.
|
||||||
|
- группировать (`GROUP BY`),
|
||||||
|
по пользователям, по возрасту пользователей, по дате заказа (когорты), по статусу заказа, по категориям.
|
||||||
|
- соединять (`JOIN`)?
|
||||||
|
по u.id, o.user_id, p.id, oi.order_id, oi.product_id
|
||||||
|
2. Для каждого поля, которое ты назвал, укажи: «индекс нужен / индекс не нужен» и коротко объясни почему (часто фильтруем, редко фильтруем, мало разных значений и т.п.).
|
||||||
|
|
||||||
|
по странам пользователя ДА
|
||||||
|
по категориям техники ДА
|
||||||
|
стомиость ДА
|
||||||
|
возраст пользователей НЕТ
|
||||||
|
статус заказа ДА
|
||||||
|
активность пользователей ДА
|
||||||
|
дата заказа ДА
|
||||||
|
по пользователям НЕТ
|
||||||
|
|
||||||
|
по u.id ДА
|
||||||
|
o.user_id ДА
|
||||||
|
p.id ДА
|
||||||
|
oi.order_id ДА
|
||||||
|
oi.product_id ДА
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- Задание 2. Свяжи запрос и индекс
|
||||||
|
|
||||||
|
**Придумай и напиши по одному SQL‑запросу для ситуаций:**
|
||||||
|
|
||||||
|
1. Найти пользователя по `email`.
|
||||||
|
SELECT
|
||||||
|
name,
|
||||||
|
email
|
||||||
|
FROM users
|
||||||
|
WHERE email = '';
|
||||||
|
Здесь лучше всего подойдет индекс по email. Ускорит WHERE.
|
||||||
|
|
||||||
|
2. Показать всех активных пользователей (`is_active = true`), зарегистрированных за последний год (`created_at`).
|
||||||
|
SELECT
|
||||||
|
name ,
|
||||||
|
created_at
|
||||||
|
FROM users
|
||||||
|
WHERE is_active = TRUE AND created_at > now() - interval '1 year';
|
||||||
|
Здесь лучше всего подойдет индекс по is_active, created_at. Ускорит WHERE.
|
||||||
|
|
||||||
|
3. Показать все товары категории `'electronics'`, отсортированные по `price` от низкой к высокой.
|
||||||
|
SELECT
|
||||||
|
name ,
|
||||||
|
price
|
||||||
|
FROM products
|
||||||
|
WHERE category = 'electronics'
|
||||||
|
ORDER BY price ASC;
|
||||||
|
Здесь лучше всего подойдет индекс по category, price. Ускорит WHERE и ORDER BY.
|
||||||
|
|
||||||
|
4. Показать все заказы конкретного пользователя за последние 30 дней по `user_id` и `order_date`.
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
o.id AS order_id,
|
||||||
|
order_date
|
||||||
|
FROM users u
|
||||||
|
LEFT JOIN orders o ON o.user_id = u.id
|
||||||
|
WHERE order_date > now() - interval '30 days' AND u.id = ;
|
||||||
|
Здесь лучше всего подойдет индекс по order_date, o.user_id, u.id. Ускорит JOIN и WHERE.
|
||||||
|
|
||||||
|
|
||||||
|
5. Для каждого заказа показать товары в нём (используя `order_items`).
|
||||||
|
SELECT
|
||||||
|
o.id AS order_id,
|
||||||
|
p.name AS product_name
|
||||||
|
FROM orders o
|
||||||
|
JOIN order_items oi ON o.id = oi.order_id
|
||||||
|
JOIN products p ON p.id = oi.product_id;
|
||||||
|
Здесь лучше всего подойдут индексы по o.id, oi.order_id, p.id, oi.product_id. Ускорит JOIN.
|
||||||
|
|
||||||
|
**После этого:**
|
||||||
|
|
||||||
|
1. Для **каждого** запроса предложи **один индекс**, который поможет его ускорить.
|
||||||
|
2. Объясни словами: по каким полям индекс, в каком порядке (если их несколько) и какие части запроса он ускорит (`WHERE`, `JOIN`, `ORDER BY`).
|
||||||
|
|
||||||
|
***(Важно: сейчас не писать `CREATE INDEX` — только понять, какие индексы логичны.)***
|
||||||
|
|
||||||
|
- Задание 3. Порядок столбцов в индексе
|
||||||
|
|
||||||
|
Смотри на таблицу `orders`.
|
||||||
|
|
||||||
|
**Запросы:**
|
||||||
|
|
||||||
|
1. Все заказы пользователя за период:
|
||||||
|
|
||||||
|
WHERE user_id = ? AND order_date BETWEEN ? AND ?
|
||||||
|
|
||||||
|
2. Все заказы за период (без фильтра по пользователю):
|
||||||
|
|
||||||
|
WHERE order_date BETWEEN ? AND ?
|
||||||
|
|
||||||
|
3. Все заказы пользователя вообще:
|
||||||
|
|
||||||
|
WHERE user_id = ?
|
||||||
|
|
||||||
|
|
||||||
|
**Вопросы:**
|
||||||
|
|
||||||
|
1. Какой **один** составной индекс по полям `user_id` и `order_date` ты бы сделал (в каком порядке)?
|
||||||
|
CREATE INDEX idx_user_od ON orders(order_date, user_id)
|
||||||
|
|
||||||
|
2. Для каких из трёх запросов этот индекс будет работать особенно хорошо, а для каких — хуже? Объясни.
|
||||||
|
Для первого особенно хорошо, потому что будет по двух индексам.
|
||||||
|
Для остальных хуже потому что они фильтруют по одному индексу.
|
||||||
|
|
||||||
|
3. Сравни варианты:
|
||||||
|
- один индекс `(user_id, order_date)`
|
||||||
|
плюс: работает быстрее если сортируем по обоим столбцам
|
||||||
|
минус: не работает вообще если сортировать по order_date
|
||||||
|
|
||||||
|
- два индекса: `(user_id)` и `(order_date)`
|
||||||
|
плюс: гибкость
|
||||||
|
минус: требует больше места, иногда делает lookup
|
||||||
|
|
||||||
|
Напиши, в чём плюс и минус каждого подхода.
|
||||||
|
|
||||||
|
- Задание 4. Лишние индексы в наших таблицах
|
||||||
|
|
||||||
|
**Для каждой таблицы:**
|
||||||
|
1. users(...)
|
||||||
|
2. products(...)
|
||||||
|
3. orders(...)
|
||||||
|
4. order_items(...)
|
||||||
|
|
||||||
|
СКОРЕЕ НУЖЕН
|
||||||
|
u.id - связь по джойну и GROUP BY
|
||||||
|
u.age - можно применить в WHERE и ORDER BY
|
||||||
|
u.country - можно применить в WHERE и GROUP BY
|
||||||
|
u.is_active - можно применить в WHERE и GROUP BY
|
||||||
|
p.id - связь по джойну и GROUP BY
|
||||||
|
p.category - можно применить в WHERE и GROUP BY
|
||||||
|
p.price - можно применить в WHERE и ORDER BY
|
||||||
|
p.in_stock - можно применить в WHERE и GROUP BY
|
||||||
|
o.id - связь по джойну
|
||||||
|
o.user_id - связь по джойну и GROUP BY
|
||||||
|
o.status - можно применить в WHERE и GROUP BY
|
||||||
|
oi.order_id - связь по джойну и GROUP BY
|
||||||
|
oi.product_id - связь по джойну и GROUP BY
|
||||||
|
oi.price_at_purchase - можно применить в WHERE и ORDER BY
|
||||||
|
o.order_date - можно применить в WHERE и ORDER BY и GROUP BY
|
||||||
|
u.created_at - можно применить в WHERE и ORDER BY и GROUP BY
|
||||||
|
НА САМОМ ДЕЛЕ ИНДЕКСОВ ОЧЕНЬ МНОГО И ЛУЧШЕ КОНЕЧНО ИХ СОКРАТИТЬ, ЧТОБЫ БД МОГЛА БЫСТРЕЕ ВЫДАВАТЬ РЕЗУЛЬТАТ, ПОЭТОМУ ВЫБОР ДОБАВЛЕНИЯ ИЛИ УДАЛЕНИЯ ИНДЕКСА БУДЕТ ЗАВИСЕТЬ ОТ ПРОИЗВОДСТВЕННЫХ ЗАДАЧ.
|
||||||
|
|
||||||
|
|
||||||
|
СКОРЕЕ НЕ НУЖЕН
|
||||||
|
oi.quantity - не имеет смысла
|
||||||
|
p.name - не имеет смысла
|
||||||
|
u.email - все различны
|
||||||
|
u.name - не имеет смысла
|
||||||
|
|
||||||
|
Почему слишком много индексов замедляет INSERT, UPDATE и DELETE?
|
||||||
|
Индексы это средства для быстрого чтения данных, в этом они помогают. Но когда нам необзодимо добавить данные в таблицу, обновить или удалить, то в таком случае, БД должна будет и обратиться к индексам и их изменить тоже, поэтому слишком большое количество индексов в системе, которая предназначена для постоянного добавления/обновления данных, является негативным фактором.
|
||||||
|
|
||||||
|
**Сделай две группы столбцов:**
|
||||||
|
|
||||||
|
- «Скорее нужен индекс»
|
||||||
|
- «Скорее не нужен индекс»
|
||||||
|
|
||||||
|
И для **каждого** столбца, который ты туда попал, коротко напиши причину.
|
||||||
|
|
||||||
|
**В конце ответь текстом:** почему слишком много индексов замедляет INSERT, UPDATE и DELETE?
|
||||||
|
|
||||||
|
- Задание 5. Что должно быть уникальным
|
||||||
|
1. В таблице `users` выбери поля, которые должны быть уникальными в реальном проекте (например, `email`).
|
||||||
|
id, email
|
||||||
|
2. В таблице `products` выбери поле, которое в реальном магазине часто делают уникальным (например, какой‑то артикул — можешь придумать колонку `sku`, даже если её нет).
|
||||||
|
Например, это может быть поле name (например, айфон 15 про 128 гб серый) или как ты сказал артикул (sku)
|
||||||
|
3. Для каждого выбранного поля напиши:
|
||||||
|
- что будет, если уникальности не будет (какие реальные проблемы могут возникнуть);
|
||||||
|
В таком случае будет коллизия, задвоение или ошибка, например, человек покупает товар с определенным артикулом, в БД попадают данные о покупке товара с этим артикулом и случайно в заказ попадет два товара с одинаковым артиклом.
|
||||||
|
|
||||||
|
- почему вместо обычного индекса логичен **уникальный** индекс.
|
||||||
|
Чтобы случайно нельзя было добавить другой товар с таким же индексом. Разные индексы будут создавать уникальность для уникального товара.
|
||||||
|
|
||||||
|
- Задание 6. Где индексы помогут, а где нет
|
||||||
|
|
||||||
|
Для каждого запроса оцени: «индекс нужен / не нужен», и если нужен — по каким полям:
|
||||||
|
|
||||||
|
1. SELECT * FROM users WHERE id = 100; нужен по полю id
|
||||||
|
2. SELECT * FROM users WHERE email = 'user@example.com'; не нужен
|
||||||
|
3. SELECT * FROM products WHERE price BETWEEN 100 AND 500; нужен по price
|
||||||
|
4. SELECT * FROM products WHERE name LIKE '%phone%'; нужен по name
|
||||||
|
5. SELECT * FROM orders WHERE status = 'DELIVERED'; нужен по status
|
||||||
|
6. SELECT * FROM order_items WHERE order_id = 123; нужен по order_id
|
||||||
|
|
||||||
|
**Ответ — текстом:**
|
||||||
|
В чём выгода индекса и где он не поможет
|
||||||
|
|
||||||
|
Индекс помогает искать данные быстрее (индекс это как содержание в книге). Индекс не поможет в тех столбцах, где все данные в столбце различаются, например email.
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
- Задание 1. Какой вид партиционирования выбрать
|
||||||
|
|
||||||
|
**Ответ текстом.**
|
||||||
|
|
||||||
|
1. Таблица `orders(id, user_id, order_date, status)` — очень большая (10 лет истории).
|
||||||
|
- Какой вид партиционирования (RANGE / LIST / HASH) ты выберешь и по какому полю?
|
||||||
|
ДУМАЮ МОЖНО СДЕЛАТЬ 2 ПАРТИЦИИ, ПЕРВАЯ RANGE ПО ПОЛЯМ USER_ID И ORDER_DATE - RANGE. И ВТОРАЯ ПАРТИЦИЯ ПО USER_ID И STATUS - LIST
|
||||||
|
|
||||||
|
- Какие типы запросов выиграют от этого выбора? Приведи 3 примера запросов.
|
||||||
|
|
||||||
|
|
||||||
|
2. Таблица `users(id, name, age, email, country, is_active, created_at)` — много пользователей по разным странам.
|
||||||
|
- Выбери вид партиционирования (можно «не партиционировать») и обоснуй.
|
||||||
|
МОЖНО ПО АЙДИ И СТРАНЕ - LIST. ПО АЙДИ И CREATE_AT - RANGE. COUNTRY И IS_ACTIVE - ЧТОБЫ ПОНИМАТЬ В КАКОЙ СТРАНЕ БОЛЬШЕ АКТИВНЫХ ПОЛЬЗОВАТЕЛЕЙ - МОЖЕТ HASH?
|
||||||
|
|
||||||
|
- Если ты выбрал LIST по `country`, какие плюсы и минусы у такого решения?
|
||||||
|
Огромное количество стран - минус.
|
||||||
|
|
||||||
|
3. Таблица логов `user_logs(id, user_id, created_at, event_type, payload)` — миллионы записей в день.
|
||||||
|
- Какой вид партиционирования по какому полю логичен (RANGE / HASH) и почему?
|
||||||
|
USER_ID и EVENT_TYPE по LIST или по HASH.
|
||||||
|
|
||||||
|
- Задание 2. Размер партиций
|
||||||
|
|
||||||
|
**Ответ текстом.**
|
||||||
|
|
||||||
|
1. Для `orders` с партиционированием по `order_date` придумай три варианта:
|
||||||
|
- партиции по годам;
|
||||||
|
- по месяцам;
|
||||||
|
- по дням.
|
||||||
|
2. Для каждого варианта напиши:
|
||||||
|
- примерно сколько будет партиций за 10 лет;
|
||||||
|
по годам - 10, по месяцам - 12, по дням 7.
|
||||||
|
|
||||||
|
- плюсы и минусы (удаление старых данных, поддержка, скорость запросов).
|
||||||
|
|
||||||
|
3. Выбери один вариант и обоснуй: почему он оптимален для большого онлайн‑магазина.
|
||||||
|
Для онлайн магазина лучше подойдет по месяцам, чтобы понимать сезонность товаров.
|
||||||
|
|
||||||
|
- Задание 3. RANGE‑партиции по дате заказов
|
||||||
|
|
||||||
|
**Уточнение**: партиционировать уже существующую таблицу муторно, проще создать новую
|
||||||
|
|
||||||
|
**Сделать в PostgreSQL:**
|
||||||
|
|
||||||
|
- создать партиционированную таблицу `orders`, разбитую методом RANGE по полю `order_date`;
|
||||||
|
CREATE TABLE orderz (
|
||||||
|
id INT GENERATED ALWAYS AS IDENTITY,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
order_date DATE NOT NULL,
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (id, order_date)
|
||||||
|
) PARTITION BY RANGE(order_date);
|
||||||
|
|
||||||
|
- создать несколько партиций (например, по годам или другим диапазонам дат), чтобы вставка строк с разными датами автоматически попадала в нужную партицию;
|
||||||
|
CREATE TABLE orderz_2023 PARTITION OF orderz
|
||||||
|
FOR VALUES FROM ('2023-01-01') TO ('2023-12-31');
|
||||||
|
|
||||||
|
CREATE TABLE orderz_2024 PARTITION OF orderz
|
||||||
|
FOR VALUES FROM ('2024-01-01') TO ('2024-12-31');
|
||||||
|
|
||||||
|
CREATE TABLE orderz_2025 PARTITION OF orderz
|
||||||
|
FOR VALUES FROM ('2025-01-01') TO ('2025-12-31');
|
||||||
|
|
||||||
|
CREATE TABLE orderz_before_2023 PARTITION OF orderz
|
||||||
|
FOR VALUES FROM ('2000-01-01') TO ('2022-12-31');
|
||||||
|
- вставить тестовые данные с разными датами;
|
||||||
|
INSERT INTO orderz (user_id, order_date, status)
|
||||||
|
VALUES
|
||||||
|
(1, '2023-04-06', 'paid'),
|
||||||
|
(2, '2024-07-17', 'decline'),
|
||||||
|
(3, '2007-11-27', 'error');
|
||||||
|
|
||||||
|
- Написать несколько запросов выборки (за один год, за несколько лет, за весь период)
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM orderz o
|
||||||
|
WHERE order_date BETWEEN ('2023-01-01') AND ('2023-12-31');
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM orderz o
|
||||||
|
WHERE order_date BETWEEN ('2022-01-01') AND ('2024-12-31');
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM orderz o;
|
||||||
|
|
||||||
|
- Объяснить, в каких случаях БД будет читать только часть партиций, а в каких — все.
|
||||||
|
Если мы выставляем определенное условие соответствующее одной партиции, то чтение будет по этой же партиции, если выставляем по нескольким, то чтение по нескольким.
|
||||||
|
|
||||||
|
- Задание 4. LIST‑партиции по стране пользователей
|
||||||
|
|
||||||
|
**Сделать в PostgreSQL:**
|
||||||
|
|
||||||
|
- создать партиционированную таблицу `users`, разделённую методом LIST по полю `country`;
|
||||||
|
CREATE TABLE userz (
|
||||||
|
id INT PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
age INT,
|
||||||
|
email TEXT NOT NULL,
|
||||||
|
country TEXT NOT NULL,
|
||||||
|
is_active BOOLEAN NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL
|
||||||
|
) PARTITION BY LIST (country);
|
||||||
|
|
||||||
|
- определить несколько партиций для разных стран или групп стран и вставить тестовые данные так, чтобы строки автоматически распределялись по этим партициям;
|
||||||
|
|
||||||
|
CREATE TABLE userz_ru PARTITION OF userz
|
||||||
|
FOR VALUES IN ('RU');
|
||||||
|
|
||||||
|
CREATE TABLE userz_us PARTITION OF userz
|
||||||
|
FOR VALUES IN ('US');
|
||||||
|
|
||||||
|
CREATE TABLE userz_other PARTITION OF userz
|
||||||
|
FOR VALUES IN (DEFAULT);
|
||||||
|
- написать запросы выборки:
|
||||||
|
- по конкретной стране (например, только пользователи из одной страны);
|
||||||
|
SELECT *
|
||||||
|
FROM userz u
|
||||||
|
WHERE country = 'RU';
|
||||||
|
|
||||||
|
- по условию, не связанному с `country` (например, все активные пользователи);
|
||||||
|
SELECT *
|
||||||
|
FROM userz u
|
||||||
|
WHERE status = 'is_active';
|
||||||
|
|
||||||
|
- Объясни, для какого запроса PostgreSQL может обращаться только к одной/нескольким партициям, а для какого придётся просматривать все партиции.
|
||||||
|
Первый запрос обращается именно к одной партиции, а второй уже ко всем, потому что вто второй нет условия по партиции 'RU'.
|
||||||
|
|
||||||
|
- Задание 5. HASH‑партиции по user_id в логах
|
||||||
|
|
||||||
|
**Сделать в PostgreSQL:**
|
||||||
|
|
||||||
|
- создать таблицу `user_logs`, партиционированную методом HASH по полю `user_id`, с несколькими партициями;
|
||||||
|
CREATE TABLE user_logz (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL,
|
||||||
|
event_type TEXT NOT NULL,
|
||||||
|
payload JSONB
|
||||||
|
) PARTITION BY HASH (user_id);
|
||||||
|
|
||||||
|
CREATE TABLE user_logz_p0 PARTITION OF user_logz
|
||||||
|
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
|
||||||
|
|
||||||
|
CREATE TABLE user_logz_p1 PARTITION OF user_logz
|
||||||
|
FOR VALUES WITH (MODULUS 4, REMAINDER 1);
|
||||||
|
|
||||||
|
CREATE TABLE user_logz_p2 PARTITION OF user_logz
|
||||||
|
FOR VALUES WITH (MODULUS 4, REMAINDER 2);
|
||||||
|
|
||||||
|
CREATE TABLE user_logz_p3 PARTITION OF user_logz
|
||||||
|
FOR VALUES WITH (MODULUS 4, REMAINDER 3);
|
||||||
|
|
||||||
|
- заполнить таблицу тестовыми строками с разными значениями `user_id`;
|
||||||
|
INSERT INTO user_logz (user_id, created_at, event_type, payload)
|
||||||
|
VALUES (10, now(), 'login', '{"ip": "127.0.0.1"}');
|
||||||
|
|
||||||
|
SELECT * FROM user_logz
|
||||||
|
WHERE created_at >= now() - interval '1 day';
|
||||||
|
|
||||||
|
- написать запрос:
|
||||||
|
- выборки всех логов по конкретному `user_id`;
|
||||||
|
SELECT * FROM user_logz WHERE user_id = 10;
|
||||||
|
|
||||||
|
- выборки всех логов за период по полю `created_at` (без фильтра по `user_id`);
|
||||||
|
SELECT * FROM user_logz
|
||||||
|
WHERE created_at >= now() - interval '1 day';
|
||||||
|
|
||||||
|
- Объясни какой запрос позволит серверу обратиться только к одной партиции, а какой заставит сканировать все партиции и почему.
|
||||||
|
Первый запрос обращается к одной партиции потому что есть условие по партиционному полю, второй именно ко всем таблицам, потому что нет условия по партиции.
|
||||||
Reference in New Issue
Block a user