Bloqueios do PostgreSQL: 2. Bloqueios de string

Na última vez, falamos sobre bloqueios no nível de objeto , em particular, sobre bloqueios nos relacionamentos. Hoje veremos como os bloqueios de linha são organizados no PostgreSQL e como eles são usados ​​em conjunto com os bloqueios de objetos. Vamos falar sobre as filas de espera e quem sai fora da curva.



Bloqueios de linha


Dispositivo


Deixe-me lembrá-lo de várias conclusões importantes do último artigo.

  • Um bloqueio deve existir em algum lugar da memória compartilhada do servidor.
  • Quanto maior a granularidade de bloqueios, menor a concorrência (contenção) entre os processos em execução simultaneamente.
  • Por outro lado, quanto maior a granularidade, mais espaço de memória é ocupado por bloqueios.

Certamente, queremos que a alteração de uma linha não bloqueie outras linhas da mesma tabela. Mas não podemos dar ao luxo de iniciar cada linha com nosso próprio bloqueio.

Existem diferentes maneiras de resolver esse problema. Em alguns DBMSs, há um aumento no nível de bloqueio: se houver muitos bloqueios no nível da linha, eles serão substituídos por mais um bloqueio geral (por exemplo, no nível da página ou na tabela inteira).

Como veremos mais adiante, o PostgreSQL também usa esse mecanismo, mas apenas para bloqueios de predicado. Bloqueios de linha são diferentes.

No PostgreSQL, as informações de que uma linha está bloqueada são armazenadas única e exclusivamente na versão da linha dentro da página de dados (e não na RAM). Ou seja, este não é um bloco no sentido usual, mas apenas um sinal. Este sinal é realmente o número da transação xmax em combinação com bits de informações adicionais; um pouco mais tarde, veremos em detalhes como isso funciona.

A vantagem é que podemos bloquear quantas linhas quisermos sem consumir nenhum recurso.

Mas há um sinal de menos : como as informações sobre o bloqueio não são apresentadas na RAM, outros processos não podem ficar na fila. E não há possibilidade de monitoramento (para calcular os bloqueios, é necessário ler a tabela inteira).

Bem, o monitoramento é bom, mas algo precisa ser feito com a fila. Para fazer isso, você ainda precisa usar bloqueios "regulares". Se precisarmos esperar até que a linha seja liberada, de fato, devemos esperar até o final da transação de bloqueio - todos os bloqueios serão liberados ao confirmar ou retroceder. E para isso, você pode solicitar um número de bloqueio de uma transação de bloqueio (que, lembro-me, é mantida pela própria transação em modo excepcional). Portanto, o número de bloqueios usados ​​é proporcional ao número de processos em execução simultaneamente e não ao número de linhas que estão sendo alteradas.

Modos Excepcionais


No total, existem 4 modos em que você pode bloquear a linha. Desses, dois modos representam bloqueios exclusivos que apenas uma transação pode reter por vez.

  • O modo FOR UPDATE implica uma alteração completa (ou exclusão) de uma linha.
  • Modo PARA NÃO ATUALIZAR CHAVE - alterando apenas os campos que não estão incluídos em índices exclusivos (em outras palavras, com essa alteração, todas as chaves estrangeiras permanecem inalteradas).

O próprio comando UPDATE seleciona o modo de bloqueio mínimo apropriado; geralmente as linhas são bloqueadas no modo FOR NO KEY UPDATE.

Como você se lembra , ao excluir ou alterar uma linha, o número da versão da transação atual é gravado no campo xmax da versão atual atual. Isso mostra que a versão da linha foi excluída por esta transação. Portanto, o mesmo número xmax é usado como sinal de bloqueio. De fato, se xmax na versão da linha corresponde a uma transação ativa (ainda não concluída) e queremos atualizar essa linha específica, precisamos aguardar a conclusão da transação, para que não seja necessário um sinal adicional.

Vamos ver Crie um quadro de contas, o mesmo que no artigo anterior.

=> CREATE TABLE accounts( acc_no integer PRIMARY KEY, amount numeric ); => INSERT INTO accounts VALUES (1, 100.00), (2, 200.00), (3, 300.00); 

Para ver as páginas, é claro, precisamos da já familiar extensão pageinspect.

 => CREATE EXTENSION pageinspect; 

Por conveniência, crie uma exibição mostrando apenas as informações em que estamos interessados: xmax e alguns bits de informações.

 => CREATE VIEW accounts_v AS SELECT '(0,'||lp||')' AS ctid, t_xmax as xmax, CASE WHEN (t_infomask & 128) > 0 THEN 't' END AS lock_only, CASE WHEN (t_infomask & 4096) > 0 THEN 't' END AS is_multi, CASE WHEN (t_infomask2 & 8192) > 0 THEN 't' END AS keys_upd, CASE WHEN (t_infomask & 16) > 0 THEN 't' END AS keyshr_lock, CASE WHEN (t_infomask & 16+64) = 16+64 THEN 't' END AS shr_lock FROM heap_page_items(get_raw_page('accounts',0)) ORDER BY lp; 

Portanto, iniciamos a transação e atualizamos o valor da primeira conta (a chave não muda) e o número da segunda conta (a chave muda):

 => BEGIN; => UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 1; => UPDATE accounts SET acc_no = 20 WHERE acc_no = 2; 

Analisamos a visão:

 => SELECT * FROM accounts_v LIMIT 2; 
  ctid | xmax | lock_only | is_multi | keys_upd | keyshr_lock | shr_lock -------+--------+-----------+----------+----------+-------------+---------- (0,1) | 530492 | | | | | (0,2) | 530492 | | | t | | (2 rows) 

O modo de bloqueio é determinado pelo bit de informação keys_updated.

O mesmo campo xmax também é usado ao bloquear uma linha com o comando SELECT FOR UPDATE, mas nesse caso um bit de informações adicionais (xmax_lock_only) é colocado, o que indica que a versão da linha está bloqueada apenas, mas não excluída, e ainda é relevante.

 => ROLLBACK; => BEGIN; => SELECT * FROM accounts WHERE acc_no = 1 FOR NO KEY UPDATE; => SELECT * FROM accounts WHERE acc_no = 2 FOR UPDATE; 

 => SELECT * FROM accounts_v LIMIT 2; 
  ctid | xmax | lock_only | is_multi | keys_upd | keyshr_lock | shr_lock -------+--------+-----------+----------+----------+-------------+---------- (0,1) | 530493 | t | | | | (0,2) | 530493 | t | | t | | (2 rows) 

 => ROLLBACK; 


Modos compartilhados


Mais dois modos representam bloqueios compartilhados que podem ser mantidos por várias transações.

  • O modo FOR SHARE é usado quando você precisa ler uma sequência, mas não deve permitir que ela seja alterada por qualquer outra transação.
  • O modo FOR KEY SHARE permite alterar a string, mas apenas campos que não são da chave. Este modo, em particular, é usado automaticamente pelo PostgreSQL ao verificar chaves estrangeiras.

Vamos ver

 => BEGIN; => SELECT * FROM accounts WHERE acc_no = 1 FOR KEY SHARE; => SELECT * FROM accounts WHERE acc_no = 2 FOR SHARE; 

Nas versões de linha, vemos:

 => SELECT * FROM accounts_v LIMIT 2; 
  ctid | xmax | lock_only | is_multi | keys_upd | keyshr_lock | shr_lock -------+--------+-----------+----------+----------+-------------+---------- (0,1) | 530494 | t | | | t | (0,2) | 530494 | t | | | t | t (2 rows) 

Em ambos os casos, o bit keyshr_lock está definido e o modo SHARE pode ser reconhecido observando mais um bit de informação.

Veja como é a matriz de compatibilidade do modo geral.

modoPARA COMPARTILHAR CHAVEPARA COMPARTILHARPARA ATUALIZAÇÃO CHAVEPARA ATUALIZAÇÃO
PARA COMPARTILHAR CHAVEX
PARA COMPARTILHARXX
PARA ATUALIZAÇÃO CHAVEXXX
PARA ATUALIZAÇÃOXXXX

Isso mostra que:

  • modos excepcionais entram em conflito entre si;
  • modos compartilhados são compatíveis entre si;
  • o modo compartilhado FOR KEY SHARE é compatível com o modo exclusivo FOR NO KEY UPDATE (ou seja, você pode atualizar os campos sem chave ao mesmo tempo e garantir que a chave não seja alterada).

Multi-transação


Até agora, pensávamos que o bloqueio fosse representado pelo número da transação de bloqueio no campo xmax. Mas os bloqueios compartilhados podem ser mantidos por várias transações e vários números não podem ser gravados no mesmo campo xmax. Como ser

Para bloqueios compartilhados, são utilizadas as chamadas multi- transações (MultiXact). Este é um grupo de transações que recebe um número separado. Esse número tem a mesma dimensão que um número de transação normal, mas os números são alocados de forma independente (ou seja, o sistema pode ter os mesmos números de transação e de várias transações). Para distinguir um do outro, outro bit de informação (xmax_is_multi) é usado e informações detalhadas sobre os membros desse grupo e os modos de bloqueio estão localizadas nos arquivos no diretório $ PGDATA / pg_multixact /. Naturalmente, os últimos dados usados ​​são armazenados em buffers na memória compartilhada do servidor para um acesso mais rápido.

Adicione aos bloqueios existentes outro excepcional executado por outra transação (podemos fazer isso, porque os modos FOR KEY SHARE e FOR NO KEY UPDATE são compatíveis entre si):

 | => BEGIN; | => UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 1; 

 => SELECT * FROM accounts_v LIMIT 2; 
  ctid | xmax | lock_only | is_multi | keys_upd | keyshr_lock | shr_lock -------+--------+-----------+----------+----------+-------------+---------- (0,1) | 61 | | t | | | (0,2) | 530494 | t | | | t | t (2 rows) 

Na primeira linha, vemos que o número usual foi substituído por um número de multitransação - isso é evidenciado pelo bit xmax_is_multi.

Para não se aprofundar nos aspectos internos da implementação de multitransações, você pode usar outra extensão que permita ver todas as informações sobre todos os tipos de bloqueios de linhas de maneira conveniente.

 => CREATE EXTENSION pgrowlocks; => SELECT * FROM pgrowlocks('accounts') \gx 
 -[ RECORD 1 ]----------------------------- locked_row | (0,1) locker | 61 multi | t xids | {530494,530495} modes | {"Key Share","No Key Update"} pids | {5892,5928} -[ RECORD 2 ]----------------------------- locked_row | (0,2) locker | 530494 multi | f xids | {530494} modes | {"For Share"} pids | {5892} 

 => COMMIT; 

 | => ROLLBACK; 

Configuração de congelamento


Como números separados são alocados para multitransações, escritas no campo xmax das versões de linha, devido ao limite da capacidade de bits do contador, eles enfrentam o mesmo problema de abrangência xid que um número regular.

Portanto, para números de transações múltiplas, também é necessário executar um análogo de congelamento - substituir os números antigos por novos (ou por um número de transação regular, se no momento do congelamento o bloqueio for mantido por apenas uma transação).

Observe que o congelamento de números de transação comuns é realizado apenas para o campo xmin (pois se a versão da linha tiver um campo xmax não vazio, será uma versão irrelevante e será limpa ou a transação xmax será cancelada e seu número não nos interessa). Mas para transações múltiplas, estamos falando do campo xmax da versão atual da linha, que pode permanecer relevante, mas é constantemente bloqueado por transações diferentes em um modo compartilhado.

Para o congelamento de transações múltiplas, são responsáveis parâmetros semelhantes aos parâmetros do congelamento usual: vacuum_multixact_freeze_min_age , vacuum_multixact_freeze_table_age , autovacuum_multixact_freeze_max_age .

Quem é o extremo?


Gradualmente, aproxime-se do doce. Vamos ver qual é a imagem dos bloqueios quando várias transações atualizarão a mesma linha.

Vamos começar criando uma visão sobre pg_locks. Primeiro, tornaremos a conclusão um pouco mais compacta e, em segundo lugar, nos restringiremos a bloqueios interessantes (na verdade, descartamos os bloqueios dos números de transações virtuais, o índice na tabela de contas, pg_locks e a própria visualização - em geral, tudo o que é irrelevante e apenas distrair).

 => CREATE VIEW locks_v AS SELECT pid, locktype, CASE locktype WHEN 'relation' THEN relation::regclass::text WHEN 'transactionid' THEN transactionid::text WHEN 'tuple' THEN relation::regclass::text||':'||tuple::text END AS lockid, mode, granted FROM pg_locks WHERE locktype in ('relation','transactionid','tuple') AND (locktype != 'relation' OR relation = 'accounts'::regclass); 

Agora inicie a primeira transação e atualize a linha.

 => BEGIN; => SELECT txid_current(), pg_backend_pid(); 
  txid_current | pg_backend_pid --------------+---------------- 530497 | 5892 (1 row) 
 => UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 1; 
 UPDATE 1 

E as fechaduras?

 => SELECT * FROM locks_v WHERE pid = 5892; 
  pid | locktype | lockid | mode | granted ------+---------------+----------+------------------+--------- 5892 | relation | accounts | RowExclusiveLock | t 5892 | transactionid | 530497 | ExclusiveLock | t (2 rows) 

A transação mantém a tabela e bloqueios de número próprios. Até agora, tudo é esperado.

Iniciamos a segunda transação e tentamos atualizar a mesma linha.

 | => BEGIN; | => SELECT txid_current(), pg_backend_pid(); 
 | txid_current | pg_backend_pid | --------------+---------------- | 530498 | 5928 | (1 row) 
 | => UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 1; 

E quanto aos bloqueios de segunda transação?

 => SELECT * FROM locks_v WHERE pid = 5928; 
  pid | locktype | lockid | mode | granted ------+---------------+------------+------------------+--------- 5928 | relation | accounts | RowExclusiveLock | t 5928 | transactionid | 530498 | ExclusiveLock | t 5928 | transactionid | 530497 | ShareLock | f 5928 | tuple | accounts:1 | ExclusiveLock | t (4 rows) 

E aqui é mais interessante. Além de bloquear a tabela e o número próprio, vemos mais dois bloqueios. A segunda transação constatou que a linha estava bloqueada primeiro e "travou" aguardando seu número (concedido = f). Mas de onde e por que o bloqueio de versão de linha (locktype = tuple) veio?

Não confunda bloqueio de versão de linha (bloqueio de tupla) e bloqueio de linha (bloqueio de linha). O primeiro é um bloqueio comum do tipo tupla, visível em pg_locks. A segunda é uma marca na página de dados: xmax e bits de informação.

Quando uma transação está prestes a alterar uma linha, ela executa a seguinte sequência de ações:

  1. Captura um bloqueio exclusivo em uma versão mutável de uma string (tupla).
  2. Se xmax e bits de informação indicarem que a linha está bloqueada, ele solicitará o bloqueio do número da transação xmax.
  3. Prescreve seu xmax e os bits de informação necessários.
  4. Libera o bloqueio da versão da linha.

Quando a linha foi atualizada pela primeira transação, ela também pegou o bloqueio da versão da linha (etapa 1), mas a liberou imediatamente (etapa 4).

Quando a segunda transação chegou, ela capturou o bloqueio da versão da linha (item 1), mas foi forçada a solicitar o bloqueio do primeiro número da transação (item 2) e ficou presa nele.

O que acontece se uma terceira transação semelhante aparecer? Ela tentará capturar o bloqueio da versão da linha (item 1) e travará já nesta etapa. Confira.

 || => BEGIN; || => SELECT txid_current(), pg_backend_pid(); 
 || txid_current | pg_backend_pid || --------------+---------------- || 530499 | 5964 || (1 row) 
 || => UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 1; 

 => SELECT * FROM locks_v WHERE pid = 5964; 
  pid | locktype | lockid | mode | granted ------+---------------+------------+------------------+--------- 5964 | relation | accounts | RowExclusiveLock | t 5964 | tuple | accounts:1 | ExclusiveLock | f 5964 | transactionid | 530499 | ExclusiveLock | t (3 rows) 

As quarta, quinta, etc. transações que desejam atualizar a mesma linha não serão diferentes da transação 3 - todas elas serão "travadas" no mesmo bloqueio de versão de linha.

Adicione outra transação ao heap.

 ||| => BEGIN; ||| => SELECT txid_current(), pg_backend_pid(); 
 ||| txid_current | pg_backend_pid ||| --------------+---------------- ||| 530500 | 6000 ||| (1 row) 
 ||| => UPDATE accounts SET amount = amount - 100.00 WHERE acc_no = 1; 

 => SELECT * FROM locks_v WHERE pid = 6000; 
  pid | locktype | lockid | mode | granted ------+---------------+------------+------------------+--------- 6000 | relation | accounts | RowExclusiveLock | t 6000 | transactionid | 530500 | ExclusiveLock | t 6000 | tuple | accounts:1 | ExclusiveLock | f (3 rows) 

Uma visão geral das expectativas atuais pode ser vista na visualização pg_stat_activity, adicionando informações sobre os processos de bloqueio:

 => SELECT pid, wait_event_type, wait_event, pg_blocking_pids(pid) FROM pg_stat_activity WHERE backend_type = 'client backend'; 
  pid | wait_event_type | wait_event | pg_blocking_pids ------+-----------------+---------------+------------------ 5892 | | | {} 5928 | Lock | transactionid | {5892} 5964 | Lock | tuple | {5928} 6000 | Lock | tuple | {5928,5964} (4 rows) 

Acontece uma espécie de “fila”, na qual existe a primeira (aquela que mantém a versão de bloqueio da corda) e todas as outras que se alinham atrás da primeira.

Por que precisamos de um design tão sofisticado? Suponha que não teríamos um bloqueio de versão para a string. Em seguida, a segunda e a terceira (e assim por diante) transações aguardariam o bloqueio do número da primeira transação. No momento da conclusão da primeira transação, o recurso bloqueado desaparece ( e o que você está fazendo aqui, eh? A transação foi encerrada ) e agora tudo depende de qual dos processos em espera será ativado pela primeira vez pelo sistema operacional e, portanto, terá tempo para bloquear a linha. Todos os outros processos também serão despertados, mas eles terão que fazer fila novamente - agora após outro processo.

Isso é preocupante com o fato de que algumas das transações podem esperar indefinidamente por sua vez, se, devido a uma infeliz coincidência de circunstâncias, ela sempre "repassará" outras transações. Em inglês, essa situação é chamada de privação de bloqueio.

No nosso caso, acontece o mesmo, mas ainda um pouco melhor: a transação que veio no segundo é garantida que terá acesso ao próximo recurso. Mas o que acontece com o seguinte (terceiro e quarto)?

Se a primeira transação for concluída com uma reversão, tudo ficará bem: as transações recebidas serão executadas na ordem em que foram alinhadas.

Mas - isso é azar - se a primeira transação for concluída com uma confirmação, não apenas o número da transação desaparecerá, mas também a versão da linha! Ou seja, a versão, é claro, permanece, mas deixa de ser relevante, e será necessário atualizar uma versão mais recente e completamente diferente (da mesma linha). O recurso, que foi a vez, desaparece e todos organizam uma corrida pela posse de um novo recurso.

Deixe a primeira transação ser concluída com confirmação.

 => COMMIT; 

A segunda transação será acordada e executará parágrafos. 3 e 4.

 | UPDATE 1 

 => SELECT * FROM locks_v WHERE pid = 5928; 
  pid | locktype | lockid | mode | granted ------+---------------+----------+------------------+--------- 5928 | relation | accounts | RowExclusiveLock | t 5928 | transactionid | 530498 | ExclusiveLock | t (2 rows) 

E a terceira transação? Ela pula a etapa 1 (porque o recurso desapareceu) e fica presa na etapa 2:

 => SELECT * FROM locks_v WHERE pid = 5964; 
  pid | locktype | lockid | mode | granted ------+---------------+----------+------------------+--------- 5964 | relation | accounts | RowExclusiveLock | t 5964 | transactionid | 530498 | ShareLock | f 5964 | transactionid | 530499 | ExclusiveLock | t (3 rows) 

E o mesmo acontece com a quarta transação:

 => SELECT * FROM locks_v WHERE pid = 6000; 
  pid | locktype | lockid | mode | granted ------+---------------+----------+------------------+--------- 6000 | relation | accounts | RowExclusiveLock | t 6000 | transactionid | 530498 | ShareLock | f 6000 | transactionid | 530500 | ExclusiveLock | t (3 rows) 

Ou seja, a terceira e a quarta transações aguardam a conclusão da segunda. A fila se transformou em uma multidão de abóboras .

Concluímos todas as transações iniciadas.

 | => COMMIT; 

 || UPDATE 1 
 || => COMMIT; 

 ||| UPDATE 1 
 ||| => COMMIT; 

Mais detalhes sobre o bloqueio de strings podem ser encontrados em README.tuplock .

Você não estava parado aqui


Portanto, a idéia de um esquema de bloqueio em dois níveis é reduzir a probabilidade de uma espera eterna por uma transação de "má sorte". No entanto, como já vimos, tal situação é bem possível. E se o aplicativo usar bloqueios compartilhados, tudo poderá ficar ainda mais triste.

Deixe a primeira transação bloquear a linha no modo compartilhado.

 => BEGIN; => SELECT txid_current(), pg_backend_pid(); 
  txid_current | pg_backend_pid --------------+---------------- 530501 | 5892 (1 row) 
 => SELECT * FROM accounts WHERE acc_no = 1 FOR SHARE; 
  acc_no | amount --------+-------- 1 | 100.00 (1 row) 

A segunda transação tenta atualizar a mesma linha, mas não pode - os modos SHARE e NO KEY UPDATE são incompatíveis.

 | => BEGIN; | => SELECT txid_current(), pg_backend_pid(); 
 | txid_current | pg_backend_pid | --------------+---------------- | 530502 | 5928 | (1 row) 
 | => UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 1; 

A segunda transação aguarda a conclusão da primeira e retém o bloqueio da versão da linha - por enquanto, tudo é como da última vez.

 => SELECT * FROM locks_v WHERE pid = 5928; 
  pid | locktype | lockid | mode | granted ------+---------------+-------------+------------------+--------- 5928 | relation | accounts | RowExclusiveLock | t 5928 | tuple | accounts:10 | ExclusiveLock | t 5928 | transactionid | 530501 | ShareLock | f 5928 | transactionid | 530502 | ExclusiveLock | t (4 rows) 

E então aparece uma terceira transação que deseja um bloqueio compartilhado. O problema é que ele não tenta capturar o bloqueio na versão da linha (uma vez que não vai mudar a linha), mas simplesmente rasteja fora da vez - é compatível com a primeira transação.

 || BEGIN || => SELECT txid_current(), pg_backend_pid(); 
 || txid_current | pg_backend_pid || --------------+---------------- || 530503 | 5964 || (1 row) 
 || => SELECT * FROM accounts WHERE acc_no = 1 FOR SHARE; 
 || acc_no | amount || --------+-------- || 1 | 100.00 || (1 row) 

E agora duas transações estão bloqueando a linha:

 => SELECT * FROM pgrowlocks('accounts') \gx 
 -[ RECORD 1 ]--------------- locked_row | (0,10) locker | 62 multi | t xids | {530501,530503} modes | {Share,Share} pids | {5892,5964} 

O que acontece agora quando a primeira transação é concluída? A segunda transação será acordada, mas verá que o bloqueio da linha não desapareceu em nenhum lugar e permanecerá novamente na “fila” - desta vez para a terceira transação:

 => COMMIT; => SELECT * FROM locks_v WHERE pid = 5928; 
  pid | locktype | lockid | mode | granted ------+---------------+-------------+------------------+--------- 5928 | relation | accounts | RowExclusiveLock | t 5928 | tuple | accounts:10 | ExclusiveLock | t 5928 | transactionid | 530503 | ShareLock | f 5928 | transactionid | 530502 | ExclusiveLock | t (4 rows) 

E somente quando a terceira transação for concluída (e se nenhum outro bloqueio compartilhado aparecer durante esse período), o segundo poderá executar a atualização.

 || => COMMIT; 

 | UPDATE 1 
 | => ROLLBACK; 

Talvez seja hora de tirar algumas conclusões práticas.

  • Atualizar a mesma linha em uma tabela ao mesmo tempo em muitos processos paralelos não é uma boa ideia.
  • Se você usar bloqueios compartilhados do tipo SHARE no aplicativo, discretamente.
  • A verificação de chaves estrangeiras não deve interferir, pois os campos das chaves geralmente não são alterados e os modos KEY SHARE e NO KEY UPDATE são compatíveis.


Pediu para não pedir emprestado


Normalmente, os comandos SQL esperam liberar os recursos necessários. Mas, às vezes, você deseja se recusar a executar o comando se o bloqueio não puder ser obtido imediatamente. Para fazer isso, comandos como SELECT, LOCK, ALTER permitem que você use a frase NOWAIT.

Por exemplo:

 => BEGIN; => UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 1; 

 | => SELECT * FROM accounts FOR UPDATE NOWAIT; 
 | ERROR: could not obtain lock on row in relation "accounts" 

O comando falha imediatamente se o recurso estiver ocupado. No código do aplicativo, esse erro pode ser interceptado e processado.

Você não pode especificar a frase NOWAIT para os comandos UPDATE e DELETE, mas pode primeiro executar SELECT FOR UPDATE NOWAIT e, se possível, atualizar ou excluir a linha.

Há outra opção para não esperar - use o comando SELECT FOR com a frase SKIP LOCKED. Esse comando pulará as linhas bloqueadas, mas processará as livres.

 | => BEGIN; | => DECLARE c CURSOR FOR | SELECT * FROM accounts ORDER BY acc_no FOR UPDATE SKIP LOCKED; | => FETCH c; 
 | acc_no | amount | --------+-------- | 2 | 200.00 | (1 row) 

Neste exemplo, a primeira linha bloqueada foi pulada e imediatamente recebemos (e bloqueamos) a segunda.

Na prática, isso permite organizar o processamento de filas multithread. Você não deve criar outro aplicativo para esse comando - se você quiser usá-lo, provavelmente perderá de vista uma solução mais simples.

 => ROLLBACK; 
 | => ROLLBACK; 

Para ser continuado .

Source: https://habr.com/ru/post/pt463819/


All Articles