diff --git a/dmedvedev/2.1.theory_bd_indexes.sql b/dmedvedev/2.1.theory_bd_indexes.sql new file mode 100644 index 0000000..f5c3991 --- /dev/null +++ b/dmedvedev/2.1.theory_bd_indexes.sql @@ -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. diff --git a/dmedvedev/2.2.theory_bd_partitions.sql b/dmedvedev/2.2.theory_bd_partitions.sql new file mode 100644 index 0000000..8be0563 --- /dev/null +++ b/dmedvedev/2.2.theory_bd_partitions.sql @@ -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'; + + - Объясни какой запрос позволит серверу обратиться только к одной партиции, а какой заставит сканировать все партиции и почему. + Первый запрос обращается к одной партиции потому что есть условие по партиционному полю, второй именно ко всем таблицам, потому что нет условия по партиции.