Coluna/Linha
... Não preciso que a integridade transacional seja mantida em toda a operação, pois sei que a coluna que estou alterando não será gravada ou lida durante a atualização.
Qualquer
UPDATE
no modelo MVCC do PostgreSQL escreve uma nova versão de toda a linha . Se as transações simultâneas mudarem qualquer coluna da mesma linha, surgem problemas de simultaneidade demorados. Detalhes no manual. Conhecendo a mesma coluna não será afetado por transações simultâneas evita algumas possíveis complicações, mas não outras. Índice
Para evitar ser desviado para uma discussão fora do tópico, vamos supor que todos os valores de status para as 35 milhões de colunas estejam atualmente configurados para o mesmo valor (não nulo), tornando um índice inútil.
Ao atualizar a tabela inteira (ou partes principais dele) Postgres nunca usa um índice . Uma varredura sequencial é mais rápida quando todas ou a maioria das linhas precisam ser lidas. Pelo contrário:a manutenção do índice significa custo adicional para o
UPDATE
. Desempenho
Por exemplo, digamos que eu tenha uma tabela chamada "pedidos" com 35 milhões de linhas e queira fazer isso:
UPDATE orders SET status = null;
Eu entendo que você está buscando uma solução mais geral (veja abaixo). Mas para abordar a questão real perguntado:Isso pode ser resolvido em uma questão de milissegundos , independentemente do tamanho da tabela:
ALTER TABLE orders DROP column status
, ADD column status text;
O manual (até Postgres 10):
Quando uma coluna é adicionada comADD COLUMN
, todas as linhas existentes na tabela são inicializadas com o valor padrão da coluna (NULL
se não houverDEFAULT
cláusula é especificada). Se não houverDEFAULT
cláusula, esta é apenas uma mudança de metadados [...]
O manual (desde o Postgres 11):
Quando uma coluna é adicionada comADD COLUMN
e umDEFAULT
não volátil for especificado, o padrão é avaliado no momento da instrução e o resultado armazenado nos metadados da tabela. Esse valor será usado para a coluna de todas as linhas existentes. Se não houverDEFAULT
for especificado, NULL será usado. Em nenhum dos casos é necessária uma reescrita da tabela.
Adicionando uma coluna com umDEFAULT
volátil ou alterar o tipo de uma coluna existente exigirá que toda a tabela e seus índices sejam reescritos. [...]
E:
ADROP COLUMN
form não remove fisicamente a coluna, mas simplesmente a torna invisível para operações SQL. As operações subseqüentes de inserção e atualização na tabela armazenarão um valor nulo para a coluna. Assim, eliminar uma coluna é rápido, mas não reduzirá imediatamente o tamanho em disco da tabela, pois o espaço ocupado pela coluna descartada não é recuperado. O espaço será recuperado ao longo do tempo à medida que as linhas existentes forem atualizadas.
Certifique-se de não ter objetos dependendo da coluna (restrições de chave estrangeira, índices, visualizações, ...). Você precisaria descartar/recriá-los. Tirando isso, pequenas operações na tabela de catálogo do sistema
pg_attribute
faça o trabalho. Requer um bloqueio exclusivo na mesa, o que pode ser um problema para cargas simultâneas pesadas. (Como Buurman enfatiza em seu comentário.) Tirando isso, a operação é uma questão de milissegundos. Se você tiver um padrão de coluna que deseja manter, adicione-o novamente em um comando separado . Fazer isso no mesmo comando aplica-o a todas as linhas imediatamente. Ver:
- Adicionar nova coluna sem bloqueio de tabela?
Para realmente aplicar o padrão, considere fazê-lo em lotes:
- O PostgreSQL otimiza a adição de colunas com DEFAULTs não NULL?
Solução geral
dblink
foi mencionado em outra resposta. Ele permite o acesso a bancos de dados Postgres "remotos" em conexões separadas implícitas. O banco de dados "remoto" pode ser o atual, conseguindo assim "transações autônomas" :o que a função escreve no banco de dados "remoto" é confirmado e não pode ser revertido. Isso permite executar uma única função que atualiza uma tabela grande em partes menores e cada parte é confirmada separadamente. Evita a sobrecarga de transações para um número muito grande de linhas e, mais importante, libera bloqueios após cada parte. Isso permite que as operações simultâneas prossigam sem muito atraso e torna os deadlocks menos prováveis.
Se você não tiver acesso simultâneo, isso dificilmente será útil - exceto para evitar
ROLLBACK
após uma exceção. Considere também SAVEPOINT
para esse caso. Isenção de responsabilidade
Em primeiro lugar, muitas pequenas transações são realmente mais caras. Isso só faz sentido para tabelas grandes . O ponto ideal depende de muitos fatores.
Se você não tiver certeza do que está fazendo:uma única transação é o método seguro . Para que isso funcione corretamente, as operações simultâneas na mesa precisam funcionar. Por exemplo:escritas simultâneas pode mover uma linha para uma partição que supostamente já está processada. Ou leituras simultâneas podem ver estados intermediários inconsistentes. Você foi avisado.
Instruções passo a passo
O módulo adicional dblink precisa ser instalado primeiro:
- Como usar (instalar) o dblink no PostgreSQL?
A configuração da conexão com o dblink depende muito da configuração do cluster de banco de dados e das políticas de segurança em vigor. Pode ser complicado. Resposta posterior relacionada com mais como se conectar com dblink :
- Inserções persistentes em uma UDF mesmo que a função seja abortada
Crie um
FOREIGN SERVER
e um USER MAPPING
conforme instruído lá para simplificar e agilizar a conexão (a menos que você já tenha uma).Assumindo uma
serial PRIMARY KEY
com ou sem algumas lacunas. CREATE OR REPLACE FUNCTION f_update_in_steps()
RETURNS void AS
$func$
DECLARE
_step int; -- size of step
_cur int; -- current ID (starting with minimum)
_max int; -- maximum ID
BEGIN
SELECT INTO _cur, _max min(order_id), max(order_id) FROM orders;
-- 100 slices (steps) hard coded
_step := ((_max - _cur) / 100) + 1; -- rounded, possibly a bit too small
-- +1 to avoid endless loop for 0
PERFORM dblink_connect('myserver'); -- your foreign server as instructed above
FOR i IN 0..200 LOOP -- 200 >> 100 to make sure we exceed _max
PERFORM dblink_exec(
$$UPDATE public.orders
SET status = 'foo'
WHERE order_id >= $$ || _cur || $$
AND order_id < $$ || _cur + _step || $$
AND status IS DISTINCT FROM 'foo'$$); -- avoid empty update
_cur := _cur + _step;
EXIT WHEN _cur > _max; -- stop when done (never loop till 200)
END LOOP;
PERFORM dblink_disconnect();
END
$func$ LANGUAGE plpgsql;
Ligar:
SELECT f_update_in_steps();
Você pode parametrizar qualquer parte de acordo com sua necessidade:o nome da tabela, nome da coluna, valor, ... apenas certifique-se de higienizar os identificadores para evitar injeção de SQL:
- Nome da tabela como parâmetro de função do PostgreSQL
Evite UPDATEs vazios:
- Como posso (ou posso) SELECT DISTINCT em várias colunas?