Почему мои индексы SQL игнорируются? - PullRequest
11 голосов
/ 09 июня 2009

У нас проблема с тем, что индексы в наших таблицах игнорируются, а вместо этого SQL Server 2000 выполняет сканирование таблиц. Мы можем форсировать использование индексов с помощью предложения WITH (INDEX=<index_name>), но предпочли бы не делать этого.

Как разработчик, я хорошо знаком с SQL Server при написании T-SQL, но профилирование и настройка производительности - не моя сильная сторона. Я ищу любые советы и указания относительно того, почему это может происходить.

Обновление:

Я должен был сказать, что мы перестроили все индексы и обновили статистику индексов.

Определение таблицы для одного из виновников выглядит следующим образом:

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

Обновлен:

В ответ на вопрос самодержавия оба эти запроса выполняют сканирование таблицы.

Обновлен:

В ответ на вопрос Кассного о 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

Ответы [ 12 ]

5 голосов
/ 09 июня 2009

Лучше всего сделать индекс , охватывающий индекс , включив в индекс CustomerID столбцы InvoiceTotal и AmountPaid . (В SQL 2005 вы должны добавить их как «включенные» столбцы ». В SQL 2000 вы должны добавить их как дополнительные ключевые столбцы.) Если вы это сделаете, я гарантирую , оптимизатор запросов будет выберите свой индекс *.

Объяснение : Кажется, что индексы всегда были бы полезны, но использование (не покрывающего) индекса сопряжено со скрытыми издержками, и это « поиск закладок », который необходимо выполнить для получения любого другие столбцы, которые могут потребоваться из основной таблицы. Этот поиск закладок является дорогостоящей операцией и (одной из возможных) причин, почему оптимизатор запросов может не захотеть использовать ваш индекс.

Благодаря включению всех необходимых столбцов в самом индексе, этот поиск закладок полностью исключается, и оптимизатору не нужно играть в эту небольшую игру, чтобы выяснить, стоит ли использовать индекс «стоит».

(*) Или я верну вам очки StackOverflow. Просто отправьте конверт с печатью и адресом ...

Редактировать: Да, если ваш первичный ключ НЕ является кластерным индексом, то обязательно сделайте это тоже !! Но даже с этим изменением создание индекса CustomerID в качестве покрывающего индекса должно повысить производительность на порядок (в 10 раз или лучше) !!

5 голосов
/ 09 июня 2009

У нас возникла проблема, когда индексы в наших таблицах игнорируются, а 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

Обновление:

Поскольку CustomerID = 2112 охватывает только 1,4% ваших строк, вам следует воспользоваться индексом.

Теперь, не могли бы вы выполнить следующий запрос:

DBCC SHOW_STATISTICS ([tblinvoices], [CustomerID])

, найдите две строки смежных строк в третьем наборе результатов, где RANGE_HI_KEY меньше и больше 2112, и разместите строки здесь?

Обновление 2:

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

Вероятно (вероятно), это потому, что это самое значение (2112) встречается в RANGE_HI_KEY, и оптимизатор видит, что оно необычно плотное (3427 значения только для 2112 против всего 911 для целого диапазон от 1668 до 2111)

Не могли бы вы сделать еще две вещи:

  1. Запустить этот запрос:

    DBCC SHOW_STATISTICS ([tblinvoices], [CustomerID])
    

    и опубликуйте первые два набора результатов.

    • Запустить этот запрос:

      ВЫБЕРИТЕ ТОП 1 CustomerID, COUNT (*) ОТ ТБЛИНВОКС ГДЕ CustomerID МЕЖДУ 1668 И 2111

    , используйте верхний CustomerID из запроса выше в исходном запросе:

    SELECT CustomerID, 
           SUM(InvoiceTotal) AS SumOfInvoiceTotal, 
           SUM(AmountPaid) AS SumOfAmountPaid 
    FROM   tblInvoices 
    WHERE  CustomerID = @Top_Customer
    GROUP BY
           CustomerID
    

    и посмотрите, какой план он создаст.

3 голосов
/ 09 июня 2009

Наиболее распространенные причины игнорирования индексов:

  • Используемые столбцы недостаточно избирательны (оптимизатор решает, что сканирование таблиц будет быстрее из-за «посещения» большого количества строк)

  • В SELECT / GROUP BY / ORDER BY задействовано большое количество столбцов, что может потребовать поиска в кластеризованном индексе после использования индекса

  • Статистика устарела (или искажена большим количеством вставок или удалений)

У вас есть регулярное задание на обслуживание индекса? (это довольно часто для отсутствия в среде Dev).

2 голосов
/ 09 июня 2009

Я думаю, что только что нашел это. Я читал комментарии к вашему вопросу, прежде чем заметил, что два запроса, которые я вам дал, должны были вызвать сканирование таблицы, и я просто хотел получить результат. Тем не менее, я заинтересовался, когда кто-то сказал, что у вас нет кластерных индексов. Я подробно прочитал ваш оператор SQL create и с удивлением заметил, что это так. Вот почему он не использует ваш индекс CustomerId.

Ваш индекс CustomerId ссылается на ваш первичный ключ InvoiceNo. Однако ваш первичный ключ не кластеризован, поэтому вам нужно будет просмотреть этот индекс, чтобы определить, где на самом деле находится строка. Сервер SQL не будет выполнять два поиска по некластерному индексу, чтобы найти строку. Это будет просто сканирование таблицы.

Сделайте ваш счет-фактуру нет кластерного индекса. Мы можем предположить, что они, как правило, будут вставлены в порядке возрастания, и, следовательно, стоимость вставки не будет намного выше. Однако стоимость вашего запроса будет намного ниже. Доллары к пончикам, тогда он будет использовать ваш индекс.


Редактировать: Мне также нравится предложение БрэдК . Это обычный трюк DBA. Как он говорит, тем не менее, сделайте этот первичный кластер так или иначе, поскольку это ПРИЧИНА вашей проблемы. Очень редко иметь таблицу без кластерного индекса. Большую часть времени он не используется, это плохая идея. Тем не менее, его индекс покрытия является улучшением на вершине кластеризации, что должно быть сделано.

2 голосов
/ 09 июня 2009

Я бы начал тестирование, чтобы увидеть, можете ли вы изменить первичный ключ на кластерный индекс . Прямо сейчас стол считается "кучей". Если вы не можете сделать это, я бы также рассмотрел создание представления с кластеризованным индексом, но сначала вам нужно изменить столбец «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
2 голосов
/ 09 июня 2009

Вы пытались добавить другие столбцы в свой индекс? то есть Счет-фактура и Сумма оплаты.

Идея состоит в том, что запрос будет "покрыт" индексом, и ему не придется возвращаться к таблице.

2 голосов
/ 09 июня 2009

Последнее сообщение от Кимберли охватывает именно эту тему: http://www.sqlskills.com/BLOGS/KIMBERLY/post/The-Tipping-Point-Query-Answers.aspx

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

1 голос
/ 09 июня 2009

Вы пробовали

exec sp_recompile tblInvoices

... просто чтобы убедиться, что вы не используете кешированный плохой план?

1 голос
/ 09 июня 2009

Некоторые другие отметили, что вашей базе данных может потребоваться обновление статистики индекса. У вас также может быть такой высокий процент строк в базе данных, что было бы быстрее последовательно читать таблицу, чем искать по диску, чтобы найти все. В SQL Server есть необычный анализатор запросов с графическим интерфейсом, который расскажет вам, что, по мнению базы данных, стоит стоимость различных видов деятельности. Вы можете открыть это и увидеть, что именно он думал.

Мы можем дать вам более точные ответы, если вы можете дать нам:

Select * from tblinvoices;
Select * from tblinvoices where CustomerID = 2112;

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

0 голосов
/ 09 июня 2009

Вы используете "SELECT * FROM ..."? Это обычно приводит к сканированию.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...