Я пытаюсь повысить производительность запроса, который обращается к таблице с ~ 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, без исключений.