Preciso manter um número de revisão exclusivo (por linha) em uma tabela document_revisions, em que o número da revisão está no escopo de um documento, para que não seja exclusivo da tabela inteira, apenas do documento relacionado.
Inicialmente, criei algo como:
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
Mas há uma condição de corrida!
Estou tentando resolvê-lo pg_advisory_lock
, mas a documentação é um pouco escassa e não a entendo completamente, e não quero bloquear algo por engano.
O seguinte é aceitável, ou estou fazendo errado, ou existe uma solução melhor?
SELECT pg_advisory_lock(123);
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
SELECT pg_advisory_unlock(123);
Não devo bloquear a linha do documento (chave1) para uma determinada operação (chave2)? Portanto, essa seria a solução adequada:
SELECT pg_advisory_lock(id, 1) FROM documents WHERE id = 123;
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
SELECT pg_advisory_unlock(id, 1) FROM documents WHERE id = 123;
Talvez eu não esteja acostumado ao PostgreSQL e um SERIAL possa ter um escopo definido, ou talvez uma sequência e nextval()
faça o trabalho melhor?
fonte
Respostas:
Supondo que você armazene todas as revisões do documento em uma tabela, uma abordagem seria não armazenar o número da revisão, mas calculá-lo com base no número de revisões armazenadas na tabela.
É, essencialmente, um valor derivado , não algo que você precisa armazenar.
Uma função da janela pode ser usada para calcular o número da revisão, algo como
e você precisará de uma coluna
change_date
para acompanhar a ordem das revisões.Por outro lado, se você tiver apenas
revision
uma propriedade do documento e indicar "quantas vezes o documento foi alterado", eu adotaria a abordagem de bloqueio otimista, algo como:Se isso atualizar 0 linhas, houve uma atualização intermediária e você precisará informar o usuário sobre isso.
Em geral, tente manter sua solução o mais simples possível. Nesse caso, por
update
instrução em vez de umaselect
seguida por umainsert
ouupdate
fonte
É garantido que o SEQUENCE seja exclusivo e seu caso de uso parecerá aplicável se o número de documentos não for muito alto (caso contrário, você terá muitas sequências para gerenciar). Use a cláusula RETURNING para obter o valor que foi gerado pela sequência. Por exemplo, usando 'A36' como um document_id:
O gerenciamento das seqüências precisará ser tratado com algum cuidado. Talvez você possa manter uma tabela separada contendo os nomes dos documentos e a sequência associada a isso
document_id
para referência ao inserir / atualizar adocument_revisions
tabela.fonte
Isso geralmente é resolvido com o bloqueio otimista:
Se a atualização retornar 0 linhas atualizadas, você perdeu a atualização porque outra pessoa já atualiza a linha.
fonte
(Cheguei a essa pergunta ao tentar redescobrir um artigo sobre esse tópico. Agora que o encontrei, estou postando aqui caso outras pessoas procurem uma opção alternativa para a resposta atualmente escolhida - dando um
row_number()
)Eu tenho esse mesmo caso de uso. Para cada registro inserido em um projeto específico em nosso SaaS, precisamos de um número único e incremental que possa ser gerado em face de
INSERT
s concorrentes e seja idealmente contínuo.Este artigo descreve uma boa solução , que resumirei aqui para facilitar e posterizar.
document_id
ecounter
.counter
seráDEFAULT 0
Alternativamente, se você já tiver umadocument
entidade que agrupa todas as versões, umcounter
poderia ser acrescentadas.BEFORE INSERT
gatilho àdocument_versions
tabela que incrementa atomicamente o contador (UPDATE document_revision_counters SET counter = counter + 1 WHERE document_id = ? RETURNING counter
) e depois defineNEW.version
esse valor do contador.Como alternativa, você pode usar um CTE para fazer isso na camada de aplicativo (embora eu prefira que seja um gatilho por uma questão de consistência):
Isso é semelhante em princípio a como você estava tentando resolvê-lo inicialmente, exceto que, modificando uma linha do contador em uma única instrução, ele bloqueia as leituras do valor obsoleto até que o mesmo
INSERT
seja confirmado.Aqui está uma transcrição
psql
mostrando isso em ação:Como você pode ver, você precisa ter cuidado com o que
INSERT
acontece, daí a versão do acionador, que se parece com isso:Isso torna
INSERT
s muito mais direto e a integridade dos dados mais robusta diante deINSERT
s originários de fontes arbitrárias:fonte