Проблема в использовании индексов с SQL Server 2005 и Hibernate - PullRequest
0 голосов
/ 22 февраля 2010

У меня проблема с запросом, сгенерированным Hibernate, который не использует индекс. Доступ к базе данных осуществляется из Java с использованием JTDS, а версия сервера - SQL Server 2005 , последний пакет обновления.

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

Ситуацию можно проверить также с помощью стандартного анализатора запросов со следующим кодом SQL:

Создание таблицы и индексов

CREATE TABLE [dbo].[TestNulls](
    [PK] [varchar](36) NOT NULL,
    [DATA] [varchar](36) NULL,
    [DATANULL] [varchar](36) NULL,
 CONSTRAINT [PK_TestNulls] PRIMARY KEY NONCLUSTERED
(
[PK] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF,     
ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

CREATE NONCLUSTERED INDEX [IDX_DATA] ON [dbo].[TestNulls]
(
[DATA] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, 
IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, 
ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [IDX_DATANULL] ON [dbo].[TestNulls]
(
[DATANULL] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, 
IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON,     
ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO

Заполните его случайными данными, используя функцию newid

declare @i as int
set @i = 0
while (@i < 500000)
begin
  set nocount on

  insert into TestNulls values(NEWID(), NEWID(), null)
  insert into TestNulls values(NEWID(), null, null)
  insert into TestNulls values(NEWID(), null, null)           
  set @i = (@i + 1)
  set nocount on
end;

Этот запрос выполняет полное сканирование таблицы

declare @p varchar(36)
set @p = NEWID()
select PK, DATA, DATANULL from TestNulls
where DATANULL = @p

Если я завершу запрос с "и DATANULL IS NOT NULL", запрос теперь использует индекс.

Требуется помощь:

  • Как заставить комбинацию JTDS / Hibernate использовать индекс (по умолчанию sendStringParametersAsUnicode уже имеет значение false)?
  • Есть ли способ добавить "и столбец не равен нулю" для всех запросов гибернации, которые используют обнуляемое поле?
  • Есть объяснение этому поведению?

С уважением Massimo

Ответы [ 2 ]

1 голос
/ 28 февраля 2010

1) Я думаю, нам следует избегать значений NULL. Просто используйте DEFAULT и поместите некоторые {00000-0000-000 ...} в качестве значения NULL. Ваш скрипт заполнения данных генерирует слишком много нулевых значений, поэтому селективность значений этого поля очень низкая. Я думаю, что SQL Server выберет сканирование, а затем использует индекс в этом случае (SQL Server автоматически выбирает использовать или не использует сам индекс). И это имеет смысл. Вы должны проанализировать свои РЕАЛЬНЫЕ данные. В любом случае вы можете заставить его просто использовать какой-то индекс. Вы можете создать хранимую процедуру для сервера SQL и затем запросить ее из спящего режима, например, или введите команду hibernate, чтобы использовать пользовательский запрос для запроса данных (я думаю, это возможно) и добавить табличную подсказку к вашему запросу для принудительного использования некоторого индекса:

INDEX (index_val [, ... n]):

select PK, DATA, DATANULL from TestNulls WITH INDEX(IDX_DATANULL)

Селективность - это «количество строк» ​​/ «количество элементов», поэтому, если у вас есть 10 000 клиентов, и вы ищете все «женщины», вы должны учитывать, что поиск вернет 10K / 2. = 5K строк, так что очень «плохая» избирательность.

Luck.

0 голосов
/ 02 марта 2010

Вы используете таблицу без кластеризованного индекса (так называемую «таблицу кучи»), которая обычно не очень эффективна для SELECT, поскольку любой значимый запрос требует либо поиска по закладкам, либо полного сканирования таблицы.

Таким образом, чтобы использовать индекс, сервер должен: 1) найти в индексе заданные значения и извлечь соответствующие идентификаторы строк, 2) извлечь строки по идентификаторам и вернуть данные.

Учитывая характер ваших данных, оптимизатор считает, что полное сканирование более эффективно.

Я бы посоветовал вам попробовать:

  1. Восстановление статистики на столе. Устаревшая статистика может привести оптимизатор к неправильным решениям.
  2. Принудительно использовать индекс через подсказку. Не забудьте проверить, действительно ли он быстрее ваших реальных данных (иногда оптимизатор знает об этом лучше вас).
  3. Создайте индекс покрытия для этого запроса, добавив некоторые данные (это сделает вставки / обновления несколько медленнее, поэтому вам следует учитывать общее влияние на систему):

    CREATE INDEX IDX_DATANULL_FULL ON TestNulls (DATANULL) ВКЛЮЧИТЬ (PK, DATA)

...