Почему SQL-запрос может быть медленным, даже если используется индекс?

Ответ

Даже при наличии индекса запрос может выполняться медленно. Вот почему:

  1. Низкая селективность индекса. Индекс неэффективен, если он построен по полю с малым количеством уникальных значений (например, пол, статус_заказа с 2-3 вариантами). В этом случае СУБД может решить, что полное сканирование таблицы будет быстрее, чем работа с индексом.

  2. Использование функций над индексированным столбцом. Применение функций к столбцу в условии WHERE делает использование индекса невозможным.

    -- ПЛОХО: Индекс по `created_at` не будет использован
    SELECT * FROM orders WHERE YEAR(created_at) = 2023;
    
    -- ХОРОШО: Индекс будет использован
    SELECT * FROM orders WHERE created_at >= '2023-01-01' AND created_at < '2024-01-01';
  3. Индекс не покрывает запрос (Not a Covering Index). Если индекс включает только поля из WHERE, но не все поля из SELECT, СУБД сначала находит нужные записи по индексу, а затем для каждой из них обращается к таблице за остальными данными. Это может быть медленно, если таких записей много.

  4. Неявное преобразование типов. Если тип данных в условии запроса не совпадает с типом столбца в таблице, СУБД может быть вынуждена преобразовывать типы, что мешает использованию индекса.

    -- user_id имеет тип INT, а в запросе передается строка. Индекс может не использоваться.
    WHERE user_id = '123'
  5. Фрагментация индекса. В таблицах с частыми операциями INSERT, UPDATE, DELETE индекс может стать фрагментированным, что снижает скорость поиска по нему. Требуется его реорганизация (REORGANIZE) или перестроение (REBUILD).

  6. Поиск по шаблону с LIKE в начале строки. Индекс не будет использоваться, если шаблон поиска начинается с %.

    -- ПЛОХО: Индекс по `name` не используется (Full Scan)
    WHERE name LIKE '%John%'
    
    -- ХОРОШО: Индекс используется (Index Range Scan)
    WHERE name LIKE 'John%'

Ответ 18+ 🔞

А, слушай, вот тебе классика жанра! Сидит такой админ, весь из себя довольный, понаставил индексов, как ёлки в лесу, и думает: "Ну теперь-то у меня всё летает, пиздец!" А потом бац — запрос тормозит, как чугунный мост. И он такой: "Какого хуя?! У меня же индекс есть!". А причина-то, блядь, может быть в какой-нибудь хитрожопой мелочи.

Вот, смотри, на какие грабли обычно наступают:

  1. Индекс по полю, где значений — три штуки. Ну, типа пол ('М', 'Ж', 'Не скажу') или статус ('новый', 'в работе', 'ёбнут'). Это называется низкая селективность. База посмотрит на такой индекс, плюнет и скажет: "Да похуй, проще всю таблицу прочитать, чем по этому говнищу скакать". И сделает Full Scan. Вот и вся магия.

  2. Запросы с функциями, блядь. Самый частый пиздец. Индекс — это как алфавитный указатель в книге. А ты в запросе этот указатель пытаешься читать через зеркало.

    -- Вот это говно. Индекс по `created_at` лежит и плачет.
    SELECT * FROM orders WHERE YEAR(created_at) = 2023;
    
    -- А вот так — уже человек. Индекс отработает как надо.
    SELECT * FROM orders WHERE created_at >= '2023-01-01' AND created_at < '2024-01-01';
  3. Индекс не покрывающий (Not a Covering Index). Представь: индекс — это оглавление с номерами страниц. Ты по нему быстро нашёл, что нужная тебе хуйня на страницах 10, 23 и 45. Но чтобы её прочитать, тебе всё равно надо листать всю книгу туда-сюда. Вот так и база: нашла ID записей по индексу, а потом ещё и в саму таблицу лезет за каждую, чтобы взять остальные поля из SELECT. Если записей много — пиздец и задержка.

  4. Неявное преобразование типов. О, это тонкий момент, ёпта! Допустим, user_id у тебя в таблице — число (INT). А в запросе ты ему суёшь строку.

    WHERE user_id = '123' -- База морщится: "Бля, надо '123' в число превратить... Ладно, проще индекс проигнорить".

    И всё, приехали. Индекс в ауте.

  5. Фрагментация. Таблицу часто обновляют, удаляют, вставляют — индекс внутри превращается в швейцарский сыр, дырок больше, чем структуры. Искать по такому — одно мучение. Его надо иногда перестраивать (REBUILD), как старый забор.

  6. LIKE с процентом в начале. Ну это анекдот уже. Индекс по строковому полу — это отсортированный список. А ты ищешь '%John%'. Это как в телефонной книге, отсортированной по фамилиям, пытаться найти всех, у кого внутри имени есть буквы "ohn". Придётся читать всю книгу, блядь. Индекс тут не помощник.

    WHERE name LIKE '%John%' -- Пиздец и Full Scan.
    WHERE name LIKE 'John%'  -- А вот так уже можно, молодца.

Вот так вот, казалось бы, индекс есть, а запрос ползёт, как улитка по шершавой поверхности. Волнение ебать! Надо всегда смотреть в план выполнения, там все эти фокусы видны как на ладони.