Статьи

Не думайте, что одна секунда быстро для выполнения запроса

Я продолжаю сталкиваться с ситуациями, когда пользователи RDBMS думают, что одна секунда для выполнения запроса — что-то почти быстрое. Совсем недавно, в этом вопросе переполнения стека:

Предложение Hibernate SQL In делает загрузку процессора до 100%

Первоначальный вопрос автора заключался в том, почему аналогичный запрос выполняется за одну секунду при выполнении в SQL Server Management Studio, тогда как (по-видимому) тот же запрос выполняется за 60 секунд при выполнении из Hibernate. Запрос выглядит примерно так:

1
2
3
select Student_Id
from Student_Table
where Roll_No in ('A101','A102','A103',.....'A250');

Там может быть много причин для этого различия. Скорее всего, где-то спрятана проблема Hibernate N + 1 . Но самое яркое сообщение здесь:

Пожалуйста, не верьте, что 1 секунда — это быстро.

Базы данных — это невероятно быстрые и простые запросы, подобные приведенному выше, которые должны выполняться практически мгновенно, даже на посредственных серверах. Даже на вашем ноутбуке! Маркус Винанд сказал, что в 80% всех проблем с производительностью все, что вам нужно сделать, это добавить этот отсутствующий индекс . И это также здесь!

Исходная таблица автора содержит только два указателя:

01
02
03
04
05
06
07
08
09
10
CREATE TABLE student_table (
       Student_Id BIGINT NOT NULL IDENTITY
     , Class_Id BIGINT NOT NULL
     , Student_First_Name VARCHAR(100) NOT NULL
     , Student_Last_Name VARCHAR(100)
     , Roll_No VARCHAR(100) NOT NULL
     , PRIMARY KEY (Student_Id)
     , CONSTRAINT UK_StudentUnique_1
         UNIQUE  (Class_Id, Roll_No)
);

Существует индекс для реализации PRIMARY KEY , и есть еще один индекс для ограничения UNIQUE , но оба индекса не очень полезны, так как предикат запроса фильтрует Roll_No , который является только вторым столбцом ограничения UNIQUE . При выполнении вышеупомянутого запроса для примерно 8 миллионов строк я получаю сканирование индекса по индексу UNIQUE и запрос выполняется за три секунды:

План-1

Эта операция «индексного сканирования» совсем не годится. Я на самом деле бегу по всему индексу, чтобы найти все применимые значения Roll_No в каждом узле листа индекса. Это хорошо объяснено на странице «Использование индекса» Люка о составных индексах.

Решение

Но хорошие новости в том, что SQL Server Management Studio дает вам немедленный совет по настройке. Просто щелкните правой кнопкой мыши план выполнения и выберите «Отсутствующие сведения об индексе…», чтобы получить следующий совет:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
/*
Missing Index Details from SQLQuery1.sql -
    LUKAS-ENVY\SQLEXPRESS.test
The Query Processor estimates that implementing the
    following index could improve the query cost
    by 87.5035%.
*/
 
/*
USE [test]
GO
CREATE NONCLUSTERED INDEX [<Name of Missing Index>]
ON [dbo].[student_table] ([Roll_No])
INCLUDE ([Student_Id])
GO
*/

Это не обязательно означает, что вышеуказанный индекс является оптимальным выбором для всех ваших запросов, но тот факт, что вы запрашиваете использование предиката для Roll_No должен быть достаточно сильным показателем того, что у вас должен быть индекс хотя бы для этого Roll_No колонка. Самый простой возможный индекс здесь просто:

1
2
CREATE INDEX i_student_roll_no
ON student_table (Roll_No);

С этим индексом мы получим операцию «Поиск индекса», которая выполняется мгновенно:

План-2

Индекс покрытия

В данном конкретном случае может быть уместен «покрывающий индекс», предложенный Владом Михалчей в его ответе . Например:

1
2
CREATE INDEX i_student_roll_no
ON student_table (Roll_No, Student_Id);

Преимущество покрывающего индекса состоит в том, что вся информация, необходимая для выполнения запроса, уже содержится в индексе . Это верно в данном конкретном случае, но также может быть опасно, так как:

  • Индексу покрытия требуется больше места, и если таблица уже большая, пространство может стать проблемой
  • Индекс покрытия добавляет значение только в том случае, если в запросе также не используются дополнительные столбцы (например, для проекций, расчетов, дополнительной фильтрации, сортировки и т. Д.). Это, вероятно, не тот случай в этом простом примере, который может быстро измениться в ближайшем будущем

Таким образом, индекс покрытия не должен быть вашим выбором по умолчанию в таких случаях. Лучше быть консервативным и добавлять только те столбцы в индексе, которые добавляют немедленное значение для фильтрации.

Вывод

Я хотел бы повторить это снова и снова:

НЕ думайте, что одна секунда — это быстро

По факту:

НЕ думайте, что что-то за 2-3 мс быстро!

Если вы не занимаетесь сложными отчетами или пакетной обработкой, где время обработки, очевидно, может занять немного больше времени, простые запросы, подобные этим, никогда не должны быть медленнее, чем мгновенные . И большую часть времени вы можете достичь этой скорости, добавив индекс.

На заметку

У автора вышеупомянутого вопроса, очевидно, есть и другие проблемы. Ничто из вышеперечисленного не объясняет разницу в скорости выполнения между Hibernate и «простым SQL». Но опять же, сообщение здесь заключается в том, что если ваш «простой SQL» уже занимает больше одной секунды, у вас есть очень низко висящий плод, который нужно исправить.