Я почти уверен, что определения таблиц близки к этому:
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
подсказкой дает следующий план:
Теперь у нас есть план, который избегает сортировки, не требует дополнительной памяти для объединения и эффективно использует параллелизм. Вы должны найти, что этот запрос работает намного лучше, чем альтернативы.
Дополнительная информация о параллелизме в моей статье Форсирование плана выполнения параллельных запросов :