O PostgreSQL não cria uma transação aninhada quando você envia um segundo BEGIN. Ele emite um aviso e mantém a transação atual; o primeiro COMMIT bem-sucedido a encerra. Use pontos de salvamento quando uma função auxiliar precisar fazer uma reversão parcial. PostgreSQL: BEGIN
Este guia ajuda um desenvolvedor que trabalha no Mac Terminal a diagnosticar uma função auxiliar que confirma uma operação de edição de artigos cedo demais. Os exemplos usam linhas sintéticas de artigos e mostram como manter um único responsável pela transação, permitindo que uma função auxiliar desfaça o próprio lote. Eles seguem a documentação atual do PostgreSQL e do node-postgres.
O que acontece quando BEGIN e COMMIT aparecem dentro de outra transação?
Os comandos SQL de transação operam na transação atual da conexão. O PostgreSQL não conta chamadas repetidas a BEGIN para aguardar chamadas correspondentes a COMMIT. Um segundo BEGIN não altera a transação; um COMMIT simples e bem-sucedido confirma essa transação atual. Executar outro COMMIT simples depois disso gera um aviso, pois não há uma transação aberta. PostgreSQL: BEGIN, PostgreSQL: COMMIT
Em uma transação saudável, o rastro dos limites é:
| Comando enviado em uma conexão | Limite resultante |
|---|---|
Primeiro BEGIN |
Abre a transação |
| Primeira inserção de artigo | Pertence a essa transação |
Segundo BEGIN |
Emite um aviso; não cria um limite interno |
| Segunda inserção de artigo | Pertence à mesma transação |
Primeiro COMMIT |
Confirma as duas inserções e encerra a transação |
| Outra instrução enviada separadamente | É executada em sua própria transação, sob o comportamento normal de autoconfirmação |
ROLLBACK posterior |
Não pode desfazer as inserções já confirmadas |
Isso explica uma falha comum em aplicativos: o chamador abre uma transação e depois invoca uma função auxiliar que envia seu próprio BEGIN e COMMIT. O COMMIT da função auxiliar também encerra a transação do chamador. Uma exceção posterior no chamador não pode restaurar a operação original, que deveria ser indivisível. Essa conclusão decorre dos limites de transação documentados pelo PostgreSQL. PostgreSQL: Transactions
Reproduzir a confirmação prematura no Mac Terminal
Você precisa de um banco de dados PostgreSQL em execução e do comando psql instalado, com as configurações de conexão funcionando. Abra o Terminal e inicie uma sessão nova com psql -X. A opção -X ignora os arquivos de inicialização, tornando esta demonstração menos dependente das personalizações locais. Execute o script a seguir nessa sessão; ele ativa explicitamente a autoconfirmação. PostgreSQL: psql
A tabela é temporária e contém apenas títulos sintéticos de artigos. ON COMMIT PRESERVE ROWS mantém as linhas disponíveis para inspeção entre os limites das transações; o PostgreSQL descarta a tabela temporária quando a sessão termina. Isso demonstra o comportamento da reversão dentro da sessão, e não o armazenamento persistente após a desconexão. PostgreSQL: CREATE TABLE
\set AUTOCOMMIT on
CREATE TEMP TABLE article_boundary_demo (
id integer PRIMARY KEY,
title text NOT NULL
) ON COMMIT PRESERVE ROWS;
BEGIN;
INSERT INTO article_boundary_demo VALUES (1, 'Opening draft');
-- Deliberately incorrect attempt to open an inner transaction.
BEGIN;
INSERT INTO article_boundary_demo VALUES (2, 'Middle draft');
COMMIT;
-- The previous COMMIT has already ended the transaction.
INSERT INTO article_boundary_demo VALUES (3, 'Closing draft');
ROLLBACK;
SELECT id, title FROM article_boundary_demo ORDER BY id;
O resultado esperado contém as três linhas. O segundo BEGIN emite um aviso de que já há uma transação em andamento. O COMMIT confirma as linhas 1 e 2; em seguida, a linha 3 é confirmada por autoconfirmação. O ROLLBACK final não tem nenhuma transação aberta para desfazer. Esses são resultados esperados, deduzidos do comportamento documentado, e não de uma execução relatada do banco de dados. PostgreSQL: BEGIN, PostgreSQL: Transactions
Mantenha esta reprodução no psql interativo, como mostrado. A forma como o cliente envia os comandos é importante: várias instruções SQL passadas juntas em uma única string com psql -c podem compartilhar uma transação implícita, a menos que comandos explícitos de transação as separem. A documentação recomenda comandos separados ou a entrada padrão quando se deseja uma execução separada. PostgreSQL: psql
Como os pontos de salvamento preservam a transação externa?
Um ponto de salvamento marca uma posição dentro de uma transação existente. ROLLBACK TO SAVEPOINT descarta as alterações posteriores a essa posição, preservando o trabalho anterior. RELEASE SAVEPOINT remove a marca e mantém as alterações restantes dentro da transação que a contém. Isso não confirma essas alterações por conta própria. PostgreSQL: SAVEPOINT, PostgreSQL: RELEASE SAVEPOINT
Execute este exemplo completo na mesma sessão ou em outra sessão nova de psql. Ele cria sua própria tabela temporária:
CREATE TEMP TABLE article_savepoint_demo (
id integer PRIMARY KEY,
title text NOT NULL
) ON COMMIT PRESERVE ROWS;
BEGIN;
INSERT INTO article_savepoint_demo VALUES (1, 'Opening draft');
SAVEPOINT optional_article;
INSERT INTO article_savepoint_demo VALUES (2, 'Optional draft');
ROLLBACK TO SAVEPOINT optional_article;
RELEASE SAVEPOINT optional_article;
INSERT INTO article_savepoint_demo VALUES (3, 'Closing draft');
COMMIT;
SELECT id, title FROM article_savepoint_demo ORDER BY id;
As linhas esperadas são 1 e 3. A linha 2 é descartada no ponto de salvamento, enquanto as linhas de abertura e fechamento permanecem parte de uma única transação até o COMMIT final. Se você substituir esse COMMIT final por ROLLBACK, as duas inserções restantes serão descartadas. PostgreSQL: SAVEPOINT, PostgreSQL: Transactions
A duração dos pontos de salvamento também importa ao compor funções auxiliares. Reverter para um ponto de salvamento deixa esse ponto nomeado disponível, mas destrói os pontos de salvamento posteriores. Liberar um ponto de salvamento o remove junto com todos os pontos de salvamento ativos criados depois dele. O exemplo libera explicitamente sua marca após a recuperação. PostgreSQL: ROLLBACK TO SAVEPOINT, PostgreSQL: RELEASE SAVEPOINT
Como as funções auxiliares do node-postgres devem compartilhar a transação?
O node-postgres exige que todas as instruções de uma transação usem a mesma instância de cliente e não fornece uma abstração automática para aninhamento de transações. Passe o cliente do chamador às funções auxiliares. Se o aplicativo usar um pool, obtenha um cliente com pool.connect(), use esse cliente durante toda a operação e libere-o depois; pool.query() pode encaminhar consultas sucessivas a clientes diferentes. node-postgres: Transactions, node-postgres: Pool
O programa completo a seguir usa um único Client direto. Seu chamador é responsável por BEGIN, COMMIT e pela ROLLBACK completa. A função auxiliar usa um ponto de salvamento em torno de um lote opcional de duas linhas. Sua segunda inserção repete deliberadamente o ID de artigo 1, causando um erro de unicidade; a recuperação descarta todo o lote opcional, inclusive a inserção anterior bem-sucedida.
Configurar e executar o exemplo completo
Use uma versão atual e compatível do Node.js com o npm instalado. Em uma pasta de trabalho, execute npm install pg e salve o programa abaixo como article-transactions.mjs. Ele usa as variáveis de ambiente de conexão existentes do PostgreSQL, incluindo PGHOST, PGPORT, PGDATABASE e PGUSER, quando configuradas. Essas configurações devem apontar para um banco de dados funcional antes da execução do exemplo. node-postgres: Welcome, node-postgres: Connecting
import pg from 'pg'
const { Client } = pg
async function addOptionalArticles(client) {
await client.query('SAVEPOINT optional_articles')
try {
await client.query(
'INSERT INTO article_demo (id, title) VALUES ($1, $2)',
[2, 'Optional draft']
)
await client.query(
'INSERT INTO article_demo (id, title) VALUES ($1, $2)',
[1, 'Duplicate draft']
)
} catch (error) {
if (error.code !== '23505') throw error
await client.query('ROLLBACK TO SAVEPOINT optional_articles')
await client.query('RELEASE SAVEPOINT optional_articles')
return false
}
await client.query('RELEASE SAVEPOINT optional_articles')
return true
}
async function main() {
const client = new Client({ connectionTimeoutMillis: 5000 })
await client.connect()
try {
await client.query(`
CREATE TEMP TABLE article_demo (
id integer PRIMARY KEY,
title text NOT NULL
) ON COMMIT PRESERVE ROWS
`)
await client.query('BEGIN ISOLATION LEVEL READ COMMITTED')
try {
await client.query(
'INSERT INTO article_demo (id, title) VALUES ($1, $2)',
[1, 'Opening draft']
)
const optionalKept = await addOptionalArticles(client)
console.log('Optional batch kept:', optionalKept)
await client.query(
'INSERT INTO article_demo (id, title) VALUES ($1, $2)',
[3, 'Closing draft']
)
await client.query('COMMIT')
} catch (error) {
try {
await client.query('ROLLBACK')
} catch (rollbackError) {
throw new AggregateError(
[error, rollbackError], 'Transaction and rollback failed'
)
}
throw error
}
const result = await client.query(
'SELECT id, title FROM article_demo ORDER BY id'
)
console.table(result.rows)
} finally {
await client.end()
}
}
main().catch(error => {
console.error(error)
process.exitCode = 1
})
Execute node article-transactions.mjs. A saída esperada informa Optional batch kept: false e mostra os IDs de artigo 1 e 3. Os valores usam a interface documentada de parâmetros query(text, values). A sintaxe do JavaScript foi verificada com o Node.js; o cenário do banco de dados foi analisado com base na documentação, mas não foi executado aqui. node-postgres: Queries
Recuperar apenas de uma falha explicitamente aceita
O PostgreSQL atribui 23505 a unique_violation e recomenda verificar códigos de erro em vez do texto das mensagens. Aqui, aceitar esse código é uma regra ilustrativa para o lote sintético opcional. Outros erros são propagados ao chamador, que tenta reverter a transação inteira. A função auxiliar retorna um resultado explícito para que o trabalho opcional descartado fique visível ao chamador. PostgreSQL: PostgreSQL Error Codes
Capturar uma exceção de JavaScript, por si só, não restaura uma transação do PostgreSQL com falha. A recuperação exige reverter para um ponto de salvamento estabelecido anteriormente ou fazer uma reversão completa. Em particular, uma transação abortada não pode ser recuperada apenas liberando seu ponto de salvamento. PostgreSQL: Transactions, PostgreSQL: RELEASE SAVEPOINT
A função auxiliar pressupõe uma transação ativa e o uso sequencial desse cliente. Ela não é um wrapper genérico para transações recursivas: uma abstração reutilizável de aninhamento também precisaria gerenciar explicitamente a propriedade da transação e os nomes dos pontos de salvamento.
Quais limites de isolamento permanecem após corrigir os limites da transação?
Os pontos de salvamento permitem reversões parciais dentro da transação atual; eles não definem uma política de isolamento separada. Escolha o nível de isolamento no limite externo. O PostgreSQL não permite alterá-lo depois da primeira consulta ou instrução de modificação de dados da transação, portanto uma função auxiliar não pode elevar o isolamento de uma transação já utilizada enviando outro BEGIN. PostgreSQL: SAVEPOINT, PostgreSQL: SET TRANSACTION
Para consultas comuns, mantenha estas distinções:
| Nível de isolamento | Limite relevante |
|---|---|
READ COMMITTED |
Consultas sucessivas podem ver alterações recém-confirmadas de outras transações. Uma transação não implica um instantâneo fixo. |
REPEATABLE READ |
As consultas compartilham o instantâneo estabelecido pela primeira instrução que não seja de controle de transação, mas veem suas próprias alterações anteriores. Ainda podem ocorrer falhas de serialização. |
SERIALIZABLE |
As transações confirmadas com sucesso têm um efeito compatível com alguma ordem serial. Os aplicativos ainda precisam tratar falhas de serialização. |
READ UNCOMMITTED |
O PostgreSQL o trata como READ COMMITTED. |
PostgreSQL: Transaction Isolation, PostgreSQL: SET TRANSACTION
Uma falha de serialização exige repetir a transação inteira, inclusive as decisões que produziram seu SQL. Repetir apenas o trabalho do ponto de salvamento da função auxiliar não atualiza o instantâneo da transação externa. O PostgreSQL identifica falhas de serialização com o SQLSTATE 40001. O programa de demonstração propaga essas falhas; ele não implementa tentativas automáticas. PostgreSQL: Transaction Isolation
Como escolher o limite correto para a função auxiliar?
Use esta sequência de decisões ao revisar um chamador e suas funções auxiliares de artigos:
- Tudo precisa ser bem-sucedido em conjunto: deixe o chamador ser responsável pela transação e passe seu cliente às funções auxiliares, que devem apenas executar consultas.
- É possível descartar um lote enquanto o chamador continua: estabeleça um ponto de salvamento antes do lote, recupere-se apenas de falhas aceitas e retorne o resultado explicitamente.
- O lote precisa ser confirmado de forma independente: use um cliente e uma transação separados. A confirmação então sobreviverá à reversão externa e não poderá atender à tarefa original, que exige que tudo seja indivisível.
- Uma execução concorrente causa uma falha de serialização: reinicie a transação inteira em vez de tratar a função auxiliar como um limite independente para novas tentativas.
Essas escolhas seguem a semântica dos pontos de salvamento do PostgreSQL e a exigência do node-postgres de manter uma transação em um único cliente. PostgreSQL: SAVEPOINT, node-postgres: Transactions, PostgreSQL: Transaction Isolation
Para a tarefa de edição de artigos mostrada aqui, mantenha um único responsável pela transação, passe um único cliente por toda a cadeia de chamadas e atribua um ponto de salvamento ao trabalho opcional. Revise os comandos de transação de todas as funções auxiliares: um COMMIT inesperado altera o limite de toda a conexão, enquanto um ponto de salvamento liberado ainda deixa o resultado final sob responsabilidade do chamador.
Fontes
BEGIN — PostgreSQL Global Development Group
COMMIT — PostgreSQL Global Development Group
3.4. Transactions — PostgreSQL Global Development Group
psql — PostgreSQL Global Development Group
CREATE TABLE — PostgreSQL Global Development Group
SAVEPOINT — PostgreSQL Global Development Group
RELEASE SAVEPOINT — PostgreSQL Global Development Group
ROLLBACK TO SAVEPOINT — PostgreSQL Global Development Group
SET TRANSACTION — PostgreSQL Global Development Group
13.2. Transaction Isolation — PostgreSQL Global Development Group
Appendix A. PostgreSQL Error Codes — PostgreSQL Global Development Group
Transactions — node-postgres
Pool — node-postgres
Welcome — node-postgres
Connecting — node-postgres
Queries — node-postgres