Индексы и транзакции
Зачем:Вы понимаете, почему запрос бывает медленным и как индекс его ускоряет, и что делает транзакция, когда сбой случается посреди двух записей. После урока вы читаете план запроса, создаёте индекс там, где он нужен, и знаете, что значит ACID.
30 минут
Озвучено синтезом речи (Qwen3-TTS), голос не принадлежит человеку.
Индекс — упорядоченная структура из значений столбца и ссылок на строки, которая ускоряет поиск. Транзакция — группа команд, выполняемая целиком или никак.
Почему запрос бывает медленным
Когда вы просите базу найти все услуги категории 7, она по умолчанию делает самое простое: читает таблицу подряд, строка за строкой, и проверяет каждую. Это называется полным просмотром (в плане запроса его зовут Seq Scan, от sequential scan). На сотне строк вы этого не заметите, на миллионе заметите очень: база прочтёт миллион строк ради десяти нужных.
Индекс: указатель в конце книги
В конце хорошей книги есть алфавитный указатель: «договор: 14, 87, 203». Вы не листаете всю книгу ради слова «договор», а смотрите в указатель и идёте на нужные страницы. Индекс в базе устроен так же: это отдельная упорядоченная структура из значений столбца и ссылок на строки.
Чаще всего это дерево: на каждом уровне значения разложены по порядку, и один шаг отбрасывает почти всё. Дерево с ветвлением в сотни ветвей находит одну строку из миллионов за три-четыре шага. Поэтому поиск по индексу почти не замедляется, когда таблица растёт в тысячу раз.
Подвигайте схему. Слайдер размера таблицы и выбор «сколько строк подходит» показывают, сколько страниц прочитает база с индексом и без него. Попробуйте самые маленькие таблицы и самые частые значения.
Запрос: SELECT * FROM items WHERE category_id = 7. Это модель, а не замер: на странице помещается 100 строк, а дерево индекса ветвится на 200.
Планировщик возьмёт индекс. Строк подходит мало, а таблица велика: идти по дереву быстрее, чем читать все 1 000 страниц. В 10 раз меньше работы.
Цена за запись: вставка одной строки пишет в таблицу, в индекс первичного ключа и ещё в дополнительные индексы, которых сейчас 0. Всего записей: 2. Лишние индексы замедляют запись и занимают место.
Текстовая альтернатива: на большой таблице с редким значением индекс читает в десятки и сотни раз меньше страниц, чем просмотр всей таблицы. На маленькой таблице или при частом значении индекс не помогает, и база читает таблицу целиком. Каждый дополнительный индекс удорожает запись.
Что вы должны были заметить.
- Выигрыш растёт вместе с таблицей. На таблице из ста строк разницы нет. На миллионе строк при редком значении индекс читает в сотни раз меньше.
- Индекс не всегда нужен. Если подходит половина таблицы (пол, да/нет, «активен»), проще прочитать её всю: ходить по индексу за каждой строкой дороже. Планировщик (часть базы, которая выбирает способ выполнения) сам это решает.
- Каждый индекс стоит денег. Вставка и изменение должны обновить и таблицу, и каждый индекс. Пять индексов это пять дополнительных записей при каждой заявке. Они же занимают место.
Правила, которые из этого следуют:
- Индекс строят под конкретный частый запрос, который тормозит, а не «на всякий случай» по каждому столбцу.
- Первичный ключ и столбцы с
UNIQUEполучают индекс сами. А вот по внешним ключам (category_id) PostgreSQL индекс сам не создаёт, и соединения по ним часто становятся быстрее после ручного индекса. - Индекс может быть составным:
ON items (category_id, day)помогает запросам, которые фильтруют по категории (и ещё и по дате). Порядок столбцов важен: такой индекс служит запросам поcategory_idи по паре, но не запросам только поday.
Проверьте себя: стоит ли строить индекс по столбцу «пол» с двумя значениями?
Почти никогда. Условие «пол = ж» подходит примерно половине таблицы, и проще прочитать таблицу подряд, чем ходить по индексу за каждой строкой.
Как посмотреть, что делает база: EXPLAIN
Угадывать не нужно: перед любым запросом можно написать слово EXPLAIN, и база покажет план, как она собирается его выполнять.
EXPLAIN SELECT * FROM items WHERE category_id = 7; В плане настоящего PostgreSQL вы увидите Seq Scan on items (читает всё) или Index Scan using … on items (идёт по индексу), а ещё оценку стоимости в условных единицах. Версия EXPLAIN ANALYZE ещё и выполняет запрос и измеряет настоящее время. В практике ниже песочница выводит упрощённую модель плана: сколько строк она прочитала. Это учебный разборщик, а не настоящий PostgreSQL, и числа в его плане другие, но видна та же разница между просмотром всей таблицы и индексом.
Транзакция: всё или ничего
Теперь вторая тема, про сбои. Покупка билета это две записи: списать место и записать заказ. Если сервер упадёт между ними, мы получим списанное место без заказа. Билет пропал, и никто не узнает. Нужно, чтобы две записи проходили вместе или никак.
Это делает транзакция: группа команд, которую база считает одним неделимым действием.
BEGIN;
UPDATE seats SET free = free - 1;
INSERT INTO orders (name, phone) VALUES ('…', '…');
COMMIT; -- всё стало окончательным BEGIN открывает транзакцию, COMMIT подтверждает все изменения разом, ROLLBACK отменяет все. Если сервер упал посреди, то до COMMIT всё откатится само. Посмотрите на вкладку «Транзакция» в схеме: режим без транзакции оставляет данные в сломанном состоянии, режим с транзакцией нет.
Свойства транзакций собирают в слово ACID:
- Atomicity (атомарность): всё или ничего.
- Consistency (согласованность): после транзакции правила данных соблюдены, внешние ключи ведут куда надо, ограничения не нарушены.
- Isolation (изолированность): две одновременные транзакции не видят чужих незавершённых правок. Это то, что в первом уроке не давало двум редакторам затереть друг друга.
- Durability (долговечность): то, что подтверждено
COMMIT, не пропадёт, даже если сразу после этого отключится питание.
У изолированности есть уровни: чем строже, тем безопаснее и тем чаще транзакциям приходится ждать друг друга. PostgreSQL по умолчанию выбирает умеренный (READ COMMITTED), а самый строгий называется SERIALIZABLE. На вашем сайте почти всегда хватает умеренного.
Последняя мелочь, но она спасает в магазинах и в записи на приём. Два покупателя нажали «купить» на последнее место одновременно. Нельзя сначала прочитать число мест, а потом записать «число минус один»: между этими шагами второй покупатель прочитает то же число. Надёжнее одной командой: UPDATE seats SET free = free - 1 WHERE free > 0, и затем проверить, изменилась ли строка. Условие и изменение выполняются неделимо, и второй покупатель получит отказ.
Практика
Вы в эпохе 2022 года: сайты теперь живут на облачных базах, а проектировщики думают про нагрузку заранее. Возьмите таблицу из двух тысяч услуг и ускорьте запрос по категории, а потом запишите заявку так, чтобы сбой её не испортил.
В странице три готовые таблицы и пять полей. В первом спросите план до индекса, во втором создайте индекс и спросите ещё раз (сколько строк просмотрено, впишите числа в строку под полем). Затем в третьем напишите транзакцию из двух вставок, в четвёртом сымитируйте сбой (без COMMIT), в пятом откатите и убедитесь, что новых заявок нет.
Что было не так: MySQL без транзакций
Долгие годы самым популярным движком таблиц в MySQL был MyISAM: быстрый, простой и без транзакций и внешних ключей. На нём работали тысячи сайтов на PHP, и сбой посреди двух записей оставлял в базе полусобранные заказы. Движок InnoDB, где всё это есть, стал стандартным только в MySQL 5.5, в конце 2010 года. Сегодня считается нормой, что база умеет транзакции, и никто не хочет объяснять покупателю, куда делся билет.
Практика
Готовлю вашу страницу…
Тест
Вопрос 1 из 7
Карточки
Эти карточки попадут в повторение, когда вы завершите урок.
Что такое индекс и как он ускоряет запрос?
Ответ
Упорядоченная структура (обычно дерево) из значений столбца и ссылок на строки. Вместо чтения всей таблицы база за несколько шагов идёт к нужным строкам.
Какую цену платят за индекс и когда он бесполезен?
Ответ
Он замедляет запись (индекс обновляется вместе с таблицей) и занимает место. Бесполезен на маленькой таблице и по столбцу, где подходит много строк, например по полу.
Как узнать, использует ли запрос индекс?
Ответ
EXPLAIN перед запросом показывает план: Seq Scan значит чтение всей таблицы, Index Scan значит переход по индексу. EXPLAIN ANALYZE ещё и выполняет запрос и измеряет время.
Что делает транзакция?
Ответ
Собирает несколько записей в одну: BEGIN, шаги, COMMIT (всё стало окончательным) или ROLLBACK (всё отменено). При сбое посередине данные остаются согласованными.
Что значит ACID?
Ответ
Atomicity: всё или ничего. Consistency: правила данных соблюдены. Isolation: одновременные транзакции не мешают друг другу. Durability: подтверждённое не пропадёт.
Источники
- PostgreSQL: Indexes только ссылка, по-английски
- Postgres Pro: Индексы только ссылка, по-русски
- PostgreSQL: Using EXPLAIN только ссылка, по-английски
- PostgreSQL: Transactions (tutorial) только ссылка, по-английски
- Use The Index, Luke! только ссылка, по-английски
Видео
Что такое SQL ИНДЕКСЫ за 10 минут (Listen IT)
Видео загрузится с youtube-nocookie.com только после нажатия.
Дальше
Теперь вы знаете: индексы, транзакции и ACID.