Recentemente, comecei a converter um projeto pessoal do Microsoft SQL Server para o PostgreSQL e fiquei surpreso com o desempenho abismal que encontrei fazendo um UPDATE JOIN
entre duas tabelas.
Suponha que eles se pareçam com:
CREATE TABLE foo (
id INTEGER NOT NULL PRIMARY KEY,
bar INTEGER NULL
);
CREATE TABLE foo2 (
id INTEGER NOT NULL PRIMARY KEY,
bar INTEGER NULL
);
No T-SQL eu faria uma atualização usando um join usando algo assim:
UPDATE foo
SET bar = t2.bar
FROM foo t1
JOIN foo2 t2
ON t1.id = t2.id;
Mas rodando no Postgres, a consulta é glacialmente lenta.
Se eu mudar para:
UPDATE foo
SET bar = t2.bar
FROM foo2 t2
WHERE foo.id = t2.id;
Isso não é um problema.
Eu entendo que a sintaxe é diferente, mas eu esperava que o otimizador de consulta resolvesse algo no mesmo estádio. Em vez disso, as coisas enlouquecem. Além das diferenças sintáticas, há uma diferença sutil entre as duas consultas que não consigo ver?
Explique os planos
Update on foo (cost=85852.43..6211995294.24 rows=338326628280 width=1027)
-> Nested Loop (cost=85852.43..6211995294.24 rows=338326628280 width=1027)
-> Seq Scan on foo (cost=0.00..145721.10 rows=582410 width=1010)
-> Materialize (cost=85852.43..247935.91 rows=580908 width=17)
-> Hash Join (cost=85852.43..241627.37 rows=580908 width=17)
Hash Cond: (t1.id = t2.id)
-> Seq Scan on foo t1 (cost=0.00..145721.10 rows=582410 width=10)
-> Hash (cost=75754.08..75754.08 rows=580908 width=15)
-> Seq Scan on foo2 t2 (cost=0.00..75754.08 rows=580908 width=15)
Update on foo (cost=87575.47..535974.25 rows=581621 width=1022)
-> Hash Join (cost=87575.47..535974.25 rows=581621 width=1022)
Hash Cond: (foo.id = t2.id)
-> Seq Scan on foo (cost=0.00..151301.17 rows=1140417 width=1011)
-> Hash (cost=75761.21..75761.21 rows=581621 width=36)
-> Seq Scan on foo2 t2 (cost=0.00..75761.21 rows=581621 width=36)
Não há condição de junção entre
foo
et1
, o implícitoCROSS JOIN
força um produto cartesiano, ou sejaO(N²)
(!) operações de atualização em vez de apenasO(N)
. E o resultado é um absurdo não determinista. O efeito também se torna aparente no plano de consulta:rows=338326628280
em vez derows=581621
(Além disso: ambos os planos foram produzidos a partir de tabelas ligeiramente diferentes, mas isso parece irrelevante para a pergunta.)Pode ser corrigido adicionando uma condição de junção como:
Bem, tecnicamente, uma
WHERE
condição, mas tudo a mesma coisa.Mas isso é apenas colocar batom em um porco. Enquanto
id
é a coluna PK de cada tabela, isso é apenas adicionar ruído. Use o comando que você já encontrou:O manual aconselha para a
FROM
cláusula deUPDATE
:E:
Essa auto-junção faz sentido (ou é mesmo necessária!) se você precisar
LEFT [OUTER] JOIN
de uma(s) tabela(s) adicional(is). Infelizmente, não há nenhuma disposição no SQL para dizer"FROM LEFT"
em um arquivoUPDATE
. Exemplo:Quero abordar parte da questão de uma perspectiva do SQL Server.
Talvez algo assim, mas provavelmente não exatamente isso. É muito fácil acabar com uma junção cruzada acidental . A documentação do SQL Server realmente não fala sobre isso, mas deveria , porque isso atrapalha as pessoas o tempo todo.
A algebrização de
UPDATE...FROM
pode ser peculiar (geralmente para compatibilidade com versões anteriores), por isso é importante ser explícito e seguir as melhores práticas. Isso significa:Extratos da mesma página de documentação:
e, revelando alguns dos problemas de algebrização:
Então o exemplo T-SQL seria:
Uma maneira de verificar se uma atualização é determinística no SQL Server é primeiro escrevê-la como um
MERGE
, que verifica atualizações não determinísticas em tempo de execução. Você não escreveria rotineiramente uma atualização simples como uma mesclagem por motivos de desempenho (e talvez porque a mesclagem tenha alguns problemas próprios).