Оптимизация соединения на большом столе


10

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


Не могли бы вы включить результат sp_spaceused 'dbo.hugetable', пожалуйста?
Будет ли

Готово, добавлено чуть выше начала таблицы определений.
Быстрый Джо Смит

Это точно так. Его нелепый размер - причина, по которой я смотрю на это.
Быстрый Джо Смит

Ответы:


5

Ваша ix_hugetableвнешность совершенно бесполезна, потому что:

  • он является кластерным индексом (ПК)
  • INCLUDE не имеет значения, потому что кластеризованный индекс включает все неключевые столбцы (неключевые значения на самом нижнем листе = INCLUDEd = что такое кластеризованный индекс)

Кроме того: - добавлен или 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, чтобы ВКЛЮЧИТЬ столбец значений, чтобы не иметь доступа к столбцу значений для кластеризованного индекса.

create nonclustered index ix_hugetable on dbo.hugetable (
    fk asc, added asc
) include(value)

Примечание. Если значение не имеет значения nullable, оно совпадает с COUNT(*)семантическим. Но для СУММ нужна фактическая стоимость, а не существование .

Например, если вы измените COUNT(value)на COUNT(DISTINCT value) без изменения индекса, он должен снова разорвать запрос, поскольку он должен обрабатывать значение как значение, а не как существование.

Запросу нужно 3 столбца: добавлено, fk, значение. Первые 2 фильтруются / объединяются, как и ключевые столбцы. значение только что используется, поэтому может быть включено. Классическое использование указателя покрытия.


Ха, у меня было в голове, что кластерные и некластеризованные индексы были добавлены в другом порядке. Я не могу поверить, что я не заметил этого, почти так же, как я не могу поверить, что это было настроено в первую очередь. Завтра я изменю кластерный индекс, а потом пойду выпить кофе, пока он перестраивается.
Быстрый Джо Смит

Я изменил индексирование и использовал команду FORCE ORDER, чтобы уменьшить количество запросов на большую таблицу, но безрезультатно. Мой вопрос был обновлен.
Быстрый Джо Смит

@Quick Джо Смит: обновил мой ответ
gbn

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

2

Определите индекс hugetableтолько для addedстолбца.

БД будет использовать индекс из нескольких частей (из нескольких столбцов) только в крайней правой части списка столбцов, поскольку он имеет значения, считающиеся слева. Ваш запрос не указывает fkв предложении where первого запроса, поэтому он игнорирует индекс.


План выполнения показывает, что индекс (ix_hugetable) находится в поиске. Или вы говорите, что этот индекс не подходит для запроса?
Быстрый Джо Смит

Индекс не подходит. Кто знает, как это «с помощью индекса». Опыт подсказывает мне, что это твоя проблема. Попробуйте и расскажите нам, как это происходит.
Богемный

@Быстрый Джо Смит - ты попробовал предложение @ Богемиана? Что, где результаты?
Ливен Керсмейкерс

2
Я не согласен: предложение ON сначала обрабатывается логически, и на практике это ГДЕ, поэтому OP сначала должен попробовать оба столбца. Нет индексации по fk вообще = сканирование кластерного индекса или поиск ключа для получения значения fk для JOIN. Можете ли вы добавить некоторые ссылки на поведение, которое вы описали тоже, пожалуйста? Специально для SQL Server, если у вас мало предыдущей истории ответов для этой СУБД. На самом деле, -1 в ретроспективе как я введите этот комментарий
gbn

2

План выполнения указывает, что вложенный цикл используется на #smalltable, и что сканирование индекса по hugetarian выполняется 480 раз (для каждой строки в #smalltable).

Это тот порядок, который я ожидал бы использовать в оптимизаторе запросов, предполагая, что цикл объединяется в правильном выборе. Альтернатива состоит в том, чтобы выполнить цикл 250M раз и выполнить поиск в таблице #temp каждый раз, что может занять несколько часов / дней.

Индекс, который вы заставляете использовать в соединении MERGE, в значительной степени равен 250 миллионам строк * «размер каждой строки» - не маленький, по крайней мере, несколько ГБ. Судя по sp_spaceusedвыходным данным, «пара ГБ» может быть довольно преуменьшением - объединение MERGE требует, чтобы вы просматривали индекс, который будет очень интенсивно вводить-выводить.


Насколько я понимаю, существует 3 типа алгоритмов соединения, и что соединение слиянием имеет лучшую производительность, когда оба входа упорядочены предикатом соединения. Правильно или нет, это результат, который я пытаюсь получить.
Быстрый Джо Смит

2
Но это еще не все. Если #smalltable имеет большое количество строк, тогда может быть целесообразно объединение слиянием. Если, как следует из названия, у него небольшое количество строк, то соединение цикла может быть правильным выбором. Представьте, что у #smalltable есть одна или две строки, и они сопоставляются с несколькими строками из другой таблицы - здесь было бы трудно оправдать объединение слиянием.
Будет ли

Я подумал, что это еще не все; Я просто не знал, что это может быть. Оптимизация базы данных не совсем моя сильная сторона, как вы, наверное, уже догадались.
Быстрый Джо Смит

@Быстрый Джо Смит - спасибо за sp_spaceused. 75 ГБ индекса и 18 ГБ данных - это не единственный индекс в таблице, ix_hugetable?
Будет ли

1
+1 будет. Планировщик в настоящее время делает правильную вещь. Проблема заключается в случайном поиске диска из-за того, как ваши таблицы кластеризованы.
Дени де Бернарди

1

Ваш индекс неверен. Смотрите индексы DOS и DonTS .

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

Попробуйте добавить кластерный индекс на hugetable(added, fk). Это должно заставить планировщика искать подходящие строки из огромной таблицы, а петли вложений или слияния соединяют их с небольшой таблицей.


Спасибо за эту ссылку. Я попробую это, когда завтра доберусь до работы.
Быстрый Джо Смит
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.