Почему мои индексы SQL игнорируются?
У нас возникла проблема, когда индексы на наших таблицах игнорируются, а SQL Server 2000 выполняет сканирование таблицы. Мы можем принудительно использовать индексы с помощью предложения WITH (INDEX=<index_name>)
, но предпочли бы не делать этого.
Как разработчик, я очень хорошо знаком с SQL Server при написании T-SQL, но профилирование и настройка производительности - не моя сильная сторона. Я ищу любые советы и рекомендации относительно того, почему это может происходить.
Update:
Я должен был сказать, что мы перестроили все индексы и обновили статистику индексов.
Определение таблицы для одного из виновников выглядит следующим образом:
CREATE TABLE [tblinvoices]
(
[CustomerID] [int] NOT NULL,
[InvoiceNo] [int] NOT NULL,
[InvoiceDate] [smalldatetime] NOT NULL,
[InvoiceTotal] [numeric](18, 2) NOT NULL,
[AmountPaid] [numeric](18, 2) NULL
CONSTRAINT [DF_tblinvoices_AmountPaid] DEFAULT (0),
[DateEntered] [smalldatetime] NULL
CONSTRAINT [DF_tblinvoices_DateEntered] DEFAULT (getdate()),
[PaymentRef] [varchar](110),
[PaymentType] [varchar](10),
[SyncStatus] [int] NULL,
[PeriodStart] [smalldatetime] NULL,
[DateIssued] [smalldatetime] NULL
CONSTRAINT [DF_tblinvoices_dateissued] DEFAULT (getdate()),
CONSTRAINT [PK_tblinvoices] PRIMARY KEY NONCLUSTERED
(
[InvoiceNo] ASC
) ON [PRIMARY]
) ON [PRIMARY]
В этой таблице есть один другой индекс (тот, который мы хотим использовать SQL):
CustomerID (Non-Unique, Non-Clustered)
Следующий запрос выполняет сканирование таблицы вместо использования индекса CustomerID
:
SELECT
CustomerID,
Sum(InvoiceTotal) AS SumOfInvoiceTotal,
Sum(AmountPaid) AS SumOfAmountPaid
FROM tblInvoices
WHERE CustomerID = 2112
GROUP BY customerID
Обновлено:
В ответ на вопрос автократии оба этих запроса выполняют сканирование таблицы.
Обновлено:
В ответ на вопрос Quassnoi о DBCC SHOW_STATISTICS
данные:
RANGE_HI_KEY RANGE_ROWS EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
1667 246 454 8 27.33333
2112 911 3427 16 56.9375
2133 914 775 16 57.125
Ответы
Ответ 1
Лучше всего сделать индекс охватывающий индекс, включив столбцы InvoiceTotal и AmountPaid в индекс CustomerID. (В SQL 2005 вы добавили бы их как "включенные" столбцы ". В SQL 2000 вы должны добавить их как дополнительные ключевые столбцы.) Если вы это сделаете, я гарантирую, что оптимизатор запросов выберет ваш индекс *.
Объяснение:
Индексы кажутся, что они всегда будут полезны, но есть скрытые затраты на использование индекса (без покрытия), и это "поиск закладки", который должен быть выполнен для извлечения любых других столбцов которые могут потребоваться из основной таблицы. Этот поиск по закладкам является дорогостоящей операцией и является (одной из возможных) причин, по которым оптимизатор запросов может не использовать ваш индекс.
Включая все необходимые столбцы в самом индексе, этот поиск по закладкам полностью исключается, и оптимизатор не должен играть в эту маленькую игру, выясняя, использует ли индекс индекс "стоит того".
(*) Или я верну ваши очки StackOverflow. Просто отправьте конверт с самоназванием, с печатью, чтобы...
Изменить: Да, если ваш первичный ключ НЕ является кластеризованным индексом, то, во что бы то ни стало, сделайте это тоже! Но даже с этим изменением, делая индекс CustomerID индексом покрытия, должен увеличить производительность на порядок (10x или лучше)!!
Ответ 2
У нас возникла проблема, когда индексы на наших таблицах игнорируются, а SQL Server 2000
выполняет сканирование таблицы.
Несмотря на 4,302
дней, прошедших с момента Aug 29, 1997
, оптимизатор SQL Server
еще не превратился в SkyNet
, и он все еще может принимать некоторые неправильные решения.
Индексные подсказки - это то, как вы, человек, помогаете искусственному интеллекту.
Если вы уверены, что собрали статистику и оптимизатор по-прежнему не прав, продолжайте, используйте подсказки.
Они являются законными, правильными, документированными и поддерживаются Microsoft
способом обеспечения требуемого плана запроса.
В вашем случае:
SELECT CustomerID,
SUM(InvoiceTotal) AS SumOfInvoiceTotal,
SUM(AmountPaid) AS SumOfAmountPaid
FROM tblInvoices
WHERE CustomerID = 2112
GROUP BY
CustomerID
оптимизатор имеет два варианта:
- Используйте индекс, который подразумевает вложенный цикл над индексом вместе с
KEY LOOKUP
для извлечения значений InvoiceTotal
и AmountPaid
- Не используйте индекс и сканируйте все строки таблиц, что быстрее в
rows fetched per second
, но больше с точки зрения общего количества строк.
Первый метод может быть или не быть быстрее второго.
Оптимизатор пытается оценить, какой метод быстрее, просмотрев статистику, которая сохраняет селективность индекса вместе с другими значениями.
Для выборочных индексов прежний метод выполняется быстрее; для неселективных, последний имеет значение.
Не удалось выполнить этот запрос:
SELECT 1 - CAST(COUNT(NULLIF(CustomerID, 2112)) AS FLOAT) / COUNT(*)
FROM tlbInvoices
Update:
Так как CustomerID = 2112
охватывает только 1,4%
ваших строк, вам следует воспользоваться индексом.
Теперь вы можете запустить следующий запрос:
DBCC SHOW_STATISTICS ([tblinvoices], [CustomerID])
найдите две строки смещений в третьем наборе результатов с RANGE_HI_KEY
меньше и больше, чем 2112
, и разместите здесь строки?
Обновление 2:
Поскольку статистика кажется правильной, мы можем только догадываться, почему оптимизатор выбирает полное сканирование таблицы в этом случае.
Вероятно (возможно) это потому, что это самое значение (2112
) встречается в RANGE_HI_KEY
, и оптимизатор видит, что он необычно плотный (3427
значения для 2112
отдельно только для 911
для всего от 1668
до 2111
)
Не могли бы вы сделать еще две вещи:
-
Запустите этот запрос:
DBCC SHOW_STATISTICS ([tblinvoices], [CustomerID])
и опубликуйте первые два набора результатов.
используйте верхний CustomerID
из запроса выше в исходном запросе:
SELECT CustomerID,
SUM(InvoiceTotal) AS SumOfInvoiceTotal,
SUM(AmountPaid) AS SumOfAmountPaid
FROM tblInvoices
WHERE CustomerID = @Top_Customer
GROUP BY
CustomerID
и посмотрите, какой план он будет сгенерировать.
Ответ 3
Наиболее распространенными причинами игнорирования индексов являются
-
Столбцы не достаточно избирательны (оптимизатор решает, что сканирование таблиц будет быстрее, из-за "посещения" большого количества строк)
-
В SELECT/GROUP BY/ORDER BY имеется большое количество столбцов и будет включать поиск в кластеризованный индекс после использования индекса
-
Статистика устаревает (или искажается большим количеством вставок или удалений)
У вас есть обычная работа по обслуживанию индекса? (это довольно часто для того, чтобы он отсутствовал в среде Dev).
Ответ 4
Последнее сообщение от Kimberly охватывает именно эту тему: http://www.sqlskills.com/BLOGS/KIMBERLY/post/The-Tipping-Point-Query-Answers.aspx
SQL Server использует оптимизатор на основе затрат, и если оптимизатор вычисляет, что стоимость поиска ключей индекса и затем поиск кластерного индекса для получения остальной части столбцов выше стоимости сканирования таблицы, тогда он будет сканировать таблицу вместо этого. Точка "опрокидывания" на самом деле удивительно низкая.
Ответ 5
Вы пытались добавить другие столбцы в свой индекс? то есть InvoiceTotal и AmountPaid.
Идея состоит в том, что запрос будет "покрыт" индексом и ему не придется ссылаться на таблицу.
Ответ 6
Я бы начал тестирование, чтобы увидеть, можете ли вы изменить первичный ключ на кластеризованный индекс. Сейчас таблица считается "кучей". Если вы не можете этого сделать, я бы также подумал о создании представления с кластеризованным индексом, но сначала вам нужно будет изменить столбец "AmountPaid" на NOT NULL. Он уже по умолчанию равен нулю, поэтому это может быть легкое изменение. Для представления я бы попробовал нечто похожее на это.
SET QUOTED_IDENTIFIER, ANSI_NULLS, ANSI_PADDING, ANSI_WARNINGS, ARITHABORT, CONCAT_NULL_YIELDS_NULL, QUOTED_IDENTIFIER ON
GO
SET NUMERIC_ROUNDABORT OFF
GO
IF EXISTS
(
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.VIEWS
WHERE TABLE_NAME = N'CustomerInvoiceSummary'
)
DROP VIEW dbo.CustomerInvoiceSummary
GO
CREATE VIEW dbo.CustomerInvoiceSummary WITH SCHEMABINDING
AS
SELECT a.CustomerID
, Sum(a.InvoiceTotal) AS SumOfInvoiceTotal
, Sum(a.AmountPaid) AS SumOfAmountPaid
, COUNT_BIG(*) AS CT
FROM dbo.tblInvoices a
GROUP BY a.CustomerID
GO
CREATE UNIQUE CLUSTERED INDEX CustomerInvoiceSummary_CLI ON dbo.CustomerInvoiceSummary ( CustomerID )
GO
Ответ 7
Думаю, я его нашел. Я читал комментарии, отправленные на ваш вопрос, прежде чем я заметил, что два запроса, которые я вам дал, должны были вызвать сканирование таблицы, и я просто хотел получить результат. Тем не менее, это привлекло мое внимание, когда кто-то сказал, что у вас нет кластеризованных индексов. Я подробно прочитал инструкцию SQL create и с удивлением заметил, что это так. Вот почему он не использует ваш индекс CustomerId.
Ваш индекс CustomerId ссылается на ваш первичный ключ InvoiceNo. Однако ваш первичный ключ не сгруппирован, поэтому вам придется искать в этом индексе, чтобы найти, где находится строка. SQL-сервер не будет выполнять два некластеризованных поиска индекса, чтобы найти строку. Это просто сканирование таблицы.
Сделайте свой InvoiceNo кластеризованным индексом. Мы можем предположить, что они, как правило, будут вставлены восходящим образом, и, следовательно, стоимость вставки будет не намного выше. Однако стоимость вашего запроса будет намного ниже. Доллары к пончикам, тогда он будет использовать ваш индекс.
Изменить: мне нравится предложение BradC. Это общий трюк DBA. Тем не менее, как он говорит, сделайте так, чтобы первичная кластеризация была, так как это ПРИЧИНА вашей проблемы. Очень редко есть таблица без кластерного индекса. Большую часть времени он не используется, это плохая идея. Тем не менее, его индекс покрытия является улучшением ON TOP кластеризации, которое должно быть выполнено.
Ответ 8
Несколько других указали, что вашей базе данных может понадобиться обновленная статистика индекса. У вас также может быть такой высокий процент строк в базе данных, что было бы быстрее последовательно читать таблицу, чем искать через диск, чтобы найти все. SQL Server имеет фантастический анализатор запросов GUI, который расскажет вам, что думает база данных о стоимости различных видов деятельности. Вы можете открыть это и посмотреть, что именно думали.
Мы можем дать вам более солидные ответы, если вы можете дать нам:
Select * from tblinvoices;
Select * from tblinvoices where CustomerID = 2112;
Используйте этот анализатор запросов и обновите свою статистику. Один последний намек: вы можете использовать подсказки индекса, чтобы заставить его использовать ваш индекс, если вы уверены, что это просто глупо, когда вы сделали все остальное.
Ответ 9
Вы пробовали
exec sp_recompile tblInvoices
... просто для того, чтобы убедиться, что вы не используете скрытый плоский план?
Ответ 10
Вы также можете попробовать выполнить СТАТИСТИКУ ОБНОВЛЕНИЯ в таблице (или таблицах), участвующих в запросе. Не то, чтобы я полностью понимал статистику в SQL, но я знаю, что иногда это делают наши администраторы баз данных (с недельным заданием, которое планируется обновить на больших и часто меняющихся таблицах).
Статистика SQL
Ответ 11
Попробуйте обновить статистику. Эти статистические данные являются основой для решений, сделанных компилятором о том, следует ли использовать индекс или нет. Они содержат информацию, такую как мощность и количество строк для каждой таблицы.
Например, если статистика не была обновлена, так как вы сделали большой объемный импорт, компилятор может по-прежнему думать, что в таблице есть только 10 строк, и не беспокоить индекс.
Ответ 12
Используете ли вы "SELECT * FROM..."? Обычно это приводит к сканированию.
Нам нужны схемы, индексы и примеры запросов, чтобы помочь больше