Tenho certeza de que as definições da tabela estão próximas disso:
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)
);
Não tenho estatísticas para essas tabelas ou para seus dados, mas pelo menos a seguir definirá a cardinalidade da tabela correta (a contagem de páginas é uma suposição):
UPDATE STATISTICS dbo.persons
WITH
ROWCOUNT = 5239842,
PAGECOUNT = 100000;
UPDATE STATISTICS dbo.households
WITH
ROWCOUNT = 1928783,
PAGECOUNT = 25000;
Análise do plano de consulta
A consulta que você tem agora é:
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;
Isso gera o plano bastante ineficiente:
Os principais problemas deste plano são a junção e a classificação do hash. Ambos exigem uma concessão de memória (a junção de hash precisa criar uma tabela de hash, e a classificação precisa de espaço para armazenar as linhas enquanto a classificação progride). O Plan Explorer mostra que esta consulta recebeu 765 MB:
É bastante memória do servidor para dedicar a uma consulta! Mais exatamente, essa concessão de memória é corrigida antes do início da execução, com base na contagem de linhas e nas estimativas de tamanho.
Se a memória for insuficiente no tempo de execução, pelo menos alguns dados para o hash e / ou classificação serão gravados no disco tempdb físico . Isso é conhecido como 'derramamento' e pode ser uma operação muito lenta. Você pode rastrear esses derramamentos (no SQL Server 2008) usando os eventos do Profiler Hash Warnings e Sort Warnings .
A estimativa para a entrada de construção da tabela de hash é muito boa:
A estimativa para a entrada de classificação é menos precisa:
Você precisaria usar o Profiler para verificar, mas suspeito que a classificação se espalhe para tempdb nesse caso. Também é possível que a tabela de hash também se espalhe, mas isso é menos claro.
Observe que a memória reservada para esta consulta é dividida entre a tabela de hash e a classificação, porque elas são executadas simultaneamente. A propriedade do plano de frações de memória mostra a quantidade relativa da concessão de memória que se espera que seja usada por cada operação.
Por que classificar e hash?
A classificação é introduzida pelo otimizador de consulta para garantir que as linhas cheguem ao operador Atualização de Índice em Cluster em ordem de chave em cluster. Isso promove o acesso seqüencial à tabela, que geralmente é muito mais eficiente que o acesso aleatório.
A junção de hash é uma opção menos óbvia, porque suas entradas são de tamanhos semelhantes (para uma primeira aproximação, pelo menos). A junção de hash é melhor quando uma entrada (a que cria a tabela de hash) é relativamente pequena.
Nesse caso, o modelo de custo do otimizador determina que a junção de hash é a mais barata das três opções (hash, mesclagem, loops aninhados).
Melhorando a performance
O modelo de custo nem sempre acerta. Ele tende a superestimar o custo da junção de mesclagem paralela, especialmente à medida que o número de encadeamentos aumenta. Podemos forçar uma junção de mesclagem com uma dica de consulta:
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);
Isso produz um plano que não requer tanta memória (porque a junção de mesclagem não precisa de uma tabela de hash):
A classificação problemática ainda está lá, porque a junção de mesclagem preserva apenas a ordem de suas chaves de junção (tempId, n), mas as chaves agrupadas são (tempId, n, sporder). Você pode achar que o plano de junção de mesclagem não tem desempenho melhor que o plano de junção de hash.
Junção de loops aninhados
Também podemos tentar uma junção de loops aninhados:
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);
O plano para esta consulta é:
Esse plano de consulta é considerado o pior pelo modelo de custo do otimizador, mas possui alguns recursos muito desejáveis. Primeiro, a junção de loops aninhados não requer uma concessão de memória. Segundo, ele pode preservar a ordem das chaves da Persons
tabela para que não seja necessária uma classificação explícita. Você pode achar que esse plano tem um desempenho relativamente bom, talvez até bom o suficiente.
Loops aninhados paralelos
A grande desvantagem do plano de loops aninhados é que ele é executado em um único encadeamento. É provável que essa consulta se beneficie do paralelismo, mas o otimizador decide que não há vantagem em fazer isso aqui. Isso também não é necessariamente correto. Infelizmente, não há dica de consulta interna para obter um plano paralelo, mas há uma maneira não documentada:
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);
A ativação do sinalizador de rastreamento 8649 com a QUERYTRACEON
dica produz este plano:
Agora, temos um plano que evita a classificação, não requer memória extra para a associação e usa o paralelismo de maneira eficaz. Você deve encontrar esta consulta com desempenho muito melhor que as alternativas.
Mais informações sobre paralelismo no meu artigo Forçando um plano de execução de consulta paralela :