Към съдържанието
wannacode

Бавни заявки

Накратко: Заявката е бавна въпреки индекса, когато колоната е във функция или в LIKE, когато взима всички колони, когато OR сравнява и колона без индекс или когато брои всичко, а пита само „има ли“.

Проблем

Индексът го има, а е бавно

Отчетът за годината брои посещенията през 2026 г. Индекс по seen_at има. Пусни заявката и виж времето:

SQLбаза: магазин

И плана:

SQLбаза: магазин

SCAN visits USING COVERING INDEX. Базата чете индекса, защото е по-малък от таблицата, но го чете целия: всичките 300 000 записа. Търсене няма.

Идеята

Колоната трябва да е сама

Индексът е подреден по seen_at. Не е подреден по substr(seen_at, 1, 4). За да провери условието, базата трябва да сметне функцията за всеки запис, а за това трябва да мине през всички.

Същото условие може да се каже с граници, при които колоната е сама от едната страна:

SQLбаза: магазин
SQLбаза: магазин

Резултатът е същият, а планът е SEARCH. Сравни времената на двете заявки.

Важи и за сметките. customer_id + 0 = 1234 дава същото като customer_id = 1234, но индексът не помага:

SQLбаза: магазин

Правилото: не пипай колоната в WHERE. Сметката се прехвърля от другата страна (price > 10 / 1.2, а не price * 1.2 > 10), а функцията върху дата става интервал.

Пробвай

Октомври

В полето е заявката от годишния отчет. Месечният отчет иска посещенията през октомври 2026 г. Напиши условието така, че да ползва индекса по seen_at.

SQLбаза: магазин

Виж го

LIKE и индексът

Изглежда, че LIKE '2026-10%' трябва да ползва индекса: началото е известно, както в указателя. Пусни плана:

SQLбаза: магазин

В SQLite е SCAN. Причината: LIKE в SQLite не прави разлика между малки и главни букви, а индексът е подреден с разлика. SQLite ползва индекс за LIKE 'начало%' само при определени условия, например ако колоната или индексът са COLLATE NOCASE. За датите сигурният начин е интервалът от предишната карта.

Шаблон, който започва с %, не може да ползва обикновен индекс в никоя база. Началото не е известно, затова няма откъде да започне търсенето:

SQLбаза: магазин
По-точно, за любопитните

Изключение от правилото за функциите е индексът по израз: CREATE INDEX idx_visits_year ON visits(substr(seen_at, 1, 4)) прави WHERE substr(seen_at, 1, 4) = '2026' отново SEARCH. Индексът помага само на заявки със същия израз.

В SQLite GLOB '2026-10*' прави разлика между малки и главни букви и ползва индекса (планът е SEARCH). Същото прави и PRAGMA case_sensitive_like = ON, но то променя поведението на всеки LIKE във връзката.

В PostgreSQL обикновеният индекс помага за LIKE 'начало%' само при колация C или ако е направен с text_pattern_ops. За търсене в средата на текста има отделни видове индекси (разширението pg_trgm). Пълнотекстовото търсене е в модула за видовете бази.

Идеята

Само нужните колони

Програмата показва на кои дати са идвали клиентите от 1000 до 1100. Две заявки с едни и същи редове. Сравни времената:

SQLбаза: магазин
SQLбаза: магазин

Втората е по-бърза, защото customer_id и seen_at са в индекса (customer_id, seen_at), и тя е покриваща: базата не отваря таблицата. SELECT * иска и product_id, затова за всеки от 6 060 записа базата отива и в таблицата. Пиши в SELECT само колоните, които програмата наистина ползва. Така по-малко данни пътуват и до програмата.

Идеята

OR и IN

OR по една и съща колона е същото като IN. SQLite ги изпълнява еднакво:

SQLбаза: магазин

Капанът е OR по различни колони. Индекс по product_id няма. Редовете с product_id = 4 може да са навсякъде в таблицата, затова базата чете всичко, и индексът за клиента не помага:

SQLбаза: магазин

Ако и двете колони имат индекс, SQLite търси във всеки поотделно и събира резултатите (в плана пише MULTI-INDEX OR). Без индекс за едната половина остава SCAN.

Идеята

„Има ли“, а не „колко“

Страницата на продукт 13 показва „Популярен“, ако някой го е разглеждал. Броенето намира всичките 12 000 посещения:

SQLбаза: магазин

На страницата трябва само „да“ или „не“. EXISTS спира на първия намерен ред:

SQLбаза: магазин

Индекс по product_id няма, но EXISTS пак е бърз, защото първото посещение на продукта е близо до началото на таблицата. Ако такъв ред няма, EXISTS също ще прочете всичко. Затова и тук индексът помага. Друг начин е SELECT 1 FROM … WHERE … LIMIT 1: празен резултат значи „не“.

Упражнение

Две задачи

1. В полето е старата заявка за 2026 г. Клиент 1234 пита колко пъти е разглеждал продукти през 2025 г. Напиши я така, че планът да е SEARCH и по клиента, и по датата.

Посещенията на клиент 1234 през 2025 г., със SEARCH по двете колони:

SQLбаза: магазин

2. Бутонът „Разгледай пак“ се показва, ако клиент 4321 е разглеждал продукт от 1 октомври 2026 г. нататък. Върни 1 или 0 в колона seen.

Има ли клиент 4321 посещения от 2026-10-01 нататък?

SQLбаза: магазин

Тест

Провери се

1. Има индекс по seen_at. Кое условие може да го ползва за търсене?
2. Защо WHERE customer_id + 0 = 1234 не ползва индекса по customer_id?
3. Програмата трябва да знае дали клиентът изобщо е разглеждал продукта. Коя заявка е най-подходяща?
4. Индекс има по customer_id, а по product_id няма. Какъв е планът на WHERE customer_id = 1234 OR product_id = 4?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·