Эффективное обновление таблицы с помощью JOIN

8

У меня есть таблица, в которой указаны данные о домохозяйствах, а в другой - данные обо всех лицах, связанных с домохозяйствами. Для таблицы домохозяйств у меня есть первичный ключ, определенный с использованием двух столбцов в нем - [tempId,n]. Для таблицы person у меня есть первичный ключ, определенный с использованием 3 его столбцов[tempId,n,sporder]

Используя сортировку, продиктованную кластерной индексацией по первичным ключам, я сгенерировал уникальный идентификатор для каждого домохозяйства [HHID]и каждой [PERID]записи о человеке (ниже приведен фрагмент для генерации PERID):

 ALTER TABLE dbo.persons
 ADD PERID INT IDENTITY
 CONSTRAINT [UQ dbo.persons HHID] UNIQUE;

Теперь мой следующий шаг - связать каждого человека с соответствующими домохозяйствами, т.е. сопоставить [PERID]с а [HHID]. Пешеходный переход между двумя таблицами основан на двух столбцах [tempId,n]. Для этого у меня есть следующее внутреннее утверждение соединения.

UPDATE t1
  SET t1.HHID = t2.HHID
  FROM dbo.persons AS t1
  INNER JOIN dbo.households AS t2
  ON t1.tempId = t2.tempId AND t1.n = t2.n;

У меня в общей сложности 1928783 домохозяйств и 5239842 человек. Время выполнения в настоящее время очень велико.

Теперь мои вопросы:

  1. Можно ли оптимизировать этот запрос дальше? В целом, каковы правила большого пальца для оптимизации запроса на соединение?
  2. Есть ли другая конструкция запроса, которая может достичь желаемого результата с лучшим временем выполнения?

Я загрузил план выполнения, сгенерированный SQL Server 2008 для всего сценария, на SQLPerformance.com

sriramn
источник

Ответы:

19

Я почти уверен, что определения таблиц близки к этому:

CREATE TABLE dbo.households
(
    tempId  integer NOT NULL,
    n       integer NOT NULL,
    HHID    integer IDENTITY NOT NULL,

    CONSTRAINT [UQ dbo.households HHID] 
        UNIQUE NONCLUSTERED (HHID),

    CONSTRAINT [PK dbo.households tempId, n]
    PRIMARY KEY CLUSTERED (tempId, n)
);

CREATE TABLE dbo.persons
(
    tempId  integer NOT NULL,
    sporder integer NOT NULL,
    n       integer NOT NULL,
    PERID   integer IDENTITY NOT NULL,
    HHID    integer NOT NULL,

    CONSTRAINT [UQ dbo.persons HHID]
        UNIQUE NONCLUSTERED (PERID),

    CONSTRAINT [PK dbo.persons tempId, n, sporder]
        PRIMARY KEY CLUSTERED (tempId, n, sporder)
);

У меня нет статистики для этих таблиц или ваших данных, но следующее по крайней мере установит правильность количества элементов таблицы (количество страниц является приблизительным):

UPDATE STATISTICS dbo.persons 
WITH 
    ROWCOUNT = 5239842, 
    PAGECOUNT = 100000;

UPDATE STATISTICS dbo.households 
WITH 
    ROWCOUNT = 1928783, 
    PAGECOUNT = 25000;

Анализ плана запроса

Теперь у вас есть запрос:

UPDATE P
SET HHID = H.HHID
FROM dbo.households AS H
JOIN dbo.persons AS P
    ON P.tempId = H.tempId
    AND P.n = H.n;

Это создает довольно неэффективный план:

План по умолчанию

Основными проблемами в этом плане являются хеш-соединение и сортировка. И то, и другое требует предоставления памяти (для хеш-соединения требуется построить хеш-таблицу, а для сортировки требуется место для хранения строк во время сортировки). Обозреватель плана показывает, что этому запросу было предоставлено 765 МБ:

Память Грант

Это достаточно много памяти сервера для одного запроса! Более того, это предоставление памяти фиксируется перед началом выполнения на основе подсчета количества строк и размера.

Если памяти окажется недостаточно во время выполнения, по крайней мере, некоторые данные для хэша и / или сортировки будут записаны на физический диск tempdb . Это называется «разливом», и это может быть очень медленной операцией. Вы можете отследить эти разливы (в SQL Server 2008), используя события Profiler Hash Warnings и Sort Warnings .

Оценка входных данных сборки хеш-таблицы очень хорошая:

Хеширование

Оценка для ввода сортировки менее точна:

Сортировка ввода

Вам придется использовать Profiler для проверки, но я подозреваю, что в этом случае сортировка переместится в tempdb . Также возможно, что хеш-таблица тоже разлится, но это менее ясно.

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

Зачем сортировать и хэш?

Сортировка вводится оптимизатором запросов, чтобы обеспечить поступление строк в оператор Clustered Index Update в порядке кластеризованных ключей. Это способствует последовательному доступу к таблице, которая часто намного эффективнее, чем произвольный доступ.

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

В этом случае модель оценки оптимизатора определяет, что хеш-соединение является более дешевым из трех вариантов (хеш, слияние, вложенные циклы).

Улучшение производительности

Модель стоимости не всегда делает это правильно. Это имеет тенденцию переоценивать стоимость параллельного объединения слиянием, особенно по мере увеличения количества потоков. Мы можем форсировать объединение с помощью подсказки запроса:

UPDATE P
SET HHID = H.HHID
FROM dbo.households AS H
JOIN dbo.persons AS P
    ON P.tempId = H.tempId
    AND P.n = H.n
OPTION (MERGE JOIN);

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

План слияния

Проблемная сортировка все еще существует, потому что объединение слиянием сохраняет только порядок своих ключей объединения (tempId, n), а кластеризованные ключи - (tempId, n, sporder). Вы можете обнаружить, что план объединения слиянием работает не лучше, чем план объединения хэшей.

Вложенные циклы

Мы также можем попробовать объединение вложенных циклов:

UPDATE P
SET HHID = H.HHID
FROM dbo.households AS H
JOIN dbo.persons AS P
    ON P.tempId = H.tempId
    AND P.n = H.n
OPTION (LOOP JOIN);

План для этого запроса:

План последовательных вложенных циклов

Этот план запроса считается наихудшим в модели оценки затрат оптимизатора, но он обладает некоторыми очень желательными функциями. Во-первых, объединение вложенных циклов не требует предоставления памяти. Во-вторых, он может сохранить порядок ключей из Personsтаблицы, так что явная сортировка не требуется. Вы можете найти, что этот план выполняет относительно хорошо, возможно, даже достаточно хорошо.

Параллельные вложенные циклы

Большой недостаток плана с вложенными циклами заключается в том, что он работает в одном потоке. Вероятно, этот запрос выигрывает от параллелизма, но оптимизатор решает, что здесь нет никакого преимущества. Это не обязательно правильно тоже. К сожалению, нет встроенного запроса подсказки для получения параллельного плана, но есть недокументированный способ:

UPDATE t1
  SET t1.HHID = t2.HHID
  FROM dbo.persons AS t1
  INNER JOIN dbo.households AS t2
  ON t1.tempId = t2.tempId AND t1.n = t2.n
OPTION (LOOP JOIN, QUERYTRACEON 8649);

Включение флага трассировки 8649 с QUERYTRACEONподсказкой дает следующий план:

План параллельных вложенных циклов

Теперь у нас есть план, который избегает сортировки, не требует дополнительной памяти для объединения и эффективно использует параллелизм. Вы должны найти, что этот запрос работает намного лучше, чем альтернативы.

Дополнительная информация о параллелизме в моей статье Форсирование плана выполнения параллельных запросов :

Пол Уайт 9
источник
1

Глядя на ваш план запроса, вполне возможно, что вашей реальной проблемой может быть не само соединение, а фактический процесс обновления.

Из того, что я вижу, вы, вероятно, будете обновлять все записи о персонале в вашей базе данных и обновлять индексы (я не могу видеть, какие другие индексы это имеют, поэтому я не знаю, может ли это быть фактором)

Если это одноразовая задача, не могли бы вы отключить индексы, запустить обновление и перестроить индексы?

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

TomGough
источник