Я пытаюсь повысить производительность запроса, который обращается к таблице с ~ 250 миллионами записей. Из моего прочтения фактического (не предполагаемого) плана выполнения первое узкое место - это запрос, который выглядит следующим образом:
select
b.stuff,
a.added,
a.value
from
dbo.hugetable a
inner join
#smalltable b on a.fk = b.pk
where
a.added between @start and @end;
См. Ниже определения используемых таблиц и индексов.
План выполнения указывает, что вложенный цикл используется на #smalltable, и что сканирование индекса по hugetarian выполняется 480 раз (для каждой строки в #smalltable). Это кажется мне обратным, поэтому я попытался использовать вместо этого объединение слиянием:
select
b.stuff,
a.added,
a.value
from
dbo.hugetable a with(index = ix_hugetable)
inner merge join
#smalltable b with(index(1)) on a.fk = b.pk
where
a.added between @start and @end;
Указанный индекс (полное определение см. Ниже) охватывает столбцы fk (предикат соединения), добавленные (используемые в предложении where) и id (бесполезные) в порядке возрастания и включающие значение .
Однако, когда я делаю это, запрос увеличивается с 2,5 до 9 минут. Я бы надеялся, что подсказки приведут к более эффективному объединению, которое выполнит только один проход по каждой таблице, но явно нет.
Любое руководство приветствуется. Дополнительная информация предоставляется при необходимости.
Обновление (2011/06/02)
Реорганизовав индексирование в таблице, я значительно повысил производительность, однако столкнулся с новым препятствием, когда дело доходит до суммирования данных в огромной таблице. Результатом является суммирование по месяцам, которое в настоящее время выглядит следующим образом:
select
b.stuff,
datediff(month, 0, a.added),
count(a.value),
sum(case when a.value > 0 else 1 end) -- this triples the running time!
from
dbo.hugetable a
inner join
#smalltable b on a.fk = b.pk
group by
b.stuff,
datediff(month, 0, a.added);
В настоящее время hugetable имеет кластерный индекс pk_hugetable (added, fk)
(первичный ключ), а некластеризованный индекс идет другим путем ix_hugetable (fk, added)
.
Без четвертого столбца, описанного выше, оптимизатор использует соединение с вложенным циклом, как и раньше, используя #smalltable в качестве внешнего ввода и поиск по некластеризованному индексу в качестве внутреннего цикла (повторное выполнение 480 раз). Меня беспокоит несоответствие между предполагаемыми строками (12 958,4) и фактическими строками (74 668 468). Относительная стоимость этих поисков составляет 45%. Продолжительность, однако, меньше минуты.
С 4-й колонкой время работы увеличивается до 4 минут. На этот раз он ищет в кластеризованном индексе (2 выполнения) ту же относительную стоимость (45%), агрегирует с помощью совпадения хеша (30%), затем выполняет хеш-соединение на #smalltable (0%).
Я не уверен относительно моего следующего курса действий. Меня беспокоит то, что ни поиск по диапазону дат, ни предикат объединения не гарантируются, или даже все, что может существенно сократить набор результатов. В большинстве случаев диапазон дат будет обрезать только 10-15% записей, а внутреннее объединение на fk может отфильтровывать, возможно, 20-30%.
В соответствии с просьбой Уилла А, результаты sp_spaceused
:
name | rows | reserved | data | index_size | unused
hugetable | 261774373 | 93552920 KB | 18373816 KB | 75167432 KB | 11672 KB
#smalltable определяется как:
create table #endpoints (
pk uniqueidentifier primary key clustered,
stuff varchar(6) null
);
В то время как dbo.hugetable определяется как:
create table dbo.hugetable (
id uniqueidentifier not null,
fk uniqueidentifier not null,
added datetime not null,
value decimal(13, 3) not null,
constraint pk_hugetable primary key clustered (
fk asc,
added asc,
id 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 ix_hugetable on dbo.hugetable (
fk asc, added asc, id asc
) include(value) 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];
Поле id является избыточным, артефакт от предыдущего администратора баз данных, который настаивал на том, что все таблицы везде должны иметь GUID, без исключений.
источник
Ответы:
Ваша
ix_hugetable
внешность совершенно бесполезна, потому что:Кроме того: - добавлен или fk должен быть первым - ID является первым = мало пользы
Попробуйте изменить кластерный ключ на
(added, fk, id)
и отпуститеix_hugetable
. Вы уже попробовали(fk, added, id)
. Если ничего другого, вы сэкономите много места на диске и обслуживание индексаДругим вариантом может быть попытка подсказки FORCE ORDER с указанием порядка таблицы без подсказок JOIN / INDEX. Я стараюсь не использовать подсказки JOIN / INDEX лично, потому что вы удаляете опции для оптимизатора. Много лет назад мне сказали (семинар с гуру SQL), что подсказка FORCE ORDER может помочь, когда у вас огромный стол. JOIN small table: YMMV 7 лет спустя ...
Да, и дайте нам знать, где живет администратор баз данных, чтобы мы могли организовать некоторую настройку перкуссии
Редактировать, после 02 июня обновления
4-й столбец не является частью некластеризованного индекса, поэтому он использует кластеризованный индекс.
Попробуйте изменить индекс NC, чтобы ВКЛЮЧИТЬ столбец значений, чтобы не иметь доступа к столбцу значений для кластеризованного индекса.
Примечание. Если значение не имеет значения nullable, оно совпадает с
COUNT(*)
семантическим. Но для СУММ нужна фактическая стоимость, а не существование .Например, если вы измените
COUNT(value)
наCOUNT(DISTINCT value)
без изменения индекса, он должен снова разорвать запрос, поскольку он должен обрабатывать значение как значение, а не как существование.Запросу нужно 3 столбца: добавлено, fk, значение. Первые 2 фильтруются / объединяются, как и ключевые столбцы. значение только что используется, поэтому может быть включено. Классическое использование указателя покрытия.
источник
Определите индекс
hugetable
только дляadded
столбца.БД будет использовать индекс из нескольких частей (из нескольких столбцов) только в крайней правой части списка столбцов, поскольку он имеет значения, считающиеся слева. Ваш запрос не указывает
fk
в предложении where первого запроса, поэтому он игнорирует индекс.источник
Это тот порядок, который я ожидал бы использовать в оптимизаторе запросов, предполагая, что цикл объединяется в правильном выборе. Альтернатива состоит в том, чтобы выполнить цикл 250M раз и выполнить поиск в таблице #temp каждый раз, что может занять несколько часов / дней.
Индекс, который вы заставляете использовать в соединении MERGE, в значительной степени равен 250 миллионам строк * «размер каждой строки» - не маленький, по крайней мере, несколько ГБ. Судя по
sp_spaceused
выходным данным, «пара ГБ» может быть довольно преуменьшением - объединение MERGE требует, чтобы вы просматривали индекс, который будет очень интенсивно вводить-выводить.источник
Ваш индекс неверен. Смотрите индексы DOS и DonTS .
На данный момент ваш единственный полезный индекс - это первичный ключ маленькой таблицы. Таким образом, единственный разумный план состоит в том, чтобы последовательно сканировать небольшой стол и зацикливать беспорядок на огромном.
Попробуйте добавить кластерный индекс на
hugetable(added, fk)
. Это должно заставить планировщика искать подходящие строки из огромной таблицы, а петли вложений или слияния соединяют их с небольшой таблицей.источник