Usar SELECT FOR UPDATE no isolamento serializável

Nesta página, descrevemos como usar a cláusula FOR UPDATE no isolamento serializável.

O mecanismo de bloqueio da cláusula FOR UPDATE é diferente para leitura repetível e isolamento serializável. Usando o isolamento serializável, quando você usa a consulta SELECT para verificar uma tabela, adicionar uma cláusula FOR UPDATE ativa bloqueios exclusivos na interseção do nível de granularidade de linha e coluna, também conhecido como nível de célula. O bloqueio permanece no lugar durante toda a transação de leitura/gravação. Durante esse período, a cláusula FOR UPDATE impede que outras transações modifiquem as células bloqueadas até que a transação atual seja concluída.

Para saber como usar a cláusula FOR UPDATE, consulte os guias de referência do GoogleSQL e do PostgreSQL FOR UPDATE.

Por que usar a cláusula FOR UPDATE

Em bancos de dados com níveis de isolamento menos rigorosos, a cláusula FOR UPDATE pode ser necessária para garantir que uma transação simultânea não atualize os dados entre a leitura dos dados e a confirmação da transação. Como o Spanner impõe a serialização por padrão, é garantido que a transação só será confirmada se os dados acessados nela não estiverem desatualizados no momento da confirmação. Portanto, a cláusula FOR UPDATE não é necessária para garantir a correção da transação no Spanner.

No entanto, em casos de uso com alta contenção de gravação, como quando várias transações estão lendo e gravando simultaneamente os mesmos dados, as transações simultâneas podem causar um aumento nos cancelamentos. Isso acontece porque quando várias transações simultâneas adquirem bloqueios compartilhados e tentam fazer upgrade para bloqueios exclusivos, elas causam um impasse. O impasse bloqueia permanentemente as transações porque cada uma está esperando que a outra libere o recurso necessário. Para progredir, o Spanner cancela todas as transações, exceto uma, para resolver o deadlock. Para mais informações, consulte Bloqueio.

Uma transação que usa a cláusula FOR UPDATE adquire o bloqueio exclusivo de forma proativa e continua a ser executada, enquanto outras transações aguardam a vez para o bloqueio. Embora o Spanner ainda possa limitar a capacidade porque as transações conflitantes só podem ser realizadas uma de cada vez, mas como o Spanner só está fazendo progresso em uma transação, ele economiza tempo que seria gasto cancelando e tentando as transações de novo.

Portanto, se for importante reduzir o número de transações anuladas em um cenário de solicitação de gravação simultânea, use a cláusula FOR UPDATE para reduzir o número geral de anulações e aumentar a eficiência da execução da carga de trabalho.

Comparação com a dica LOCK_SCANNED_RANGES

A cláusula FOR UPDATE tem uma função semelhante à dica LOCK_SCANNED_RANGES=exclusive.

Há duas diferenças principais:

  • Se você usar a dica LOCK_SCANNED_RANGES, a transação vai adquirir bloqueios exclusivos nos intervalos verificados para toda a instrução. Não é possível adquirir bloqueios exclusivos em uma subconsulta. Usar a dica de bloqueio pode resultar na aquisição de mais bloqueios do que o necessário e contribuir para a disputa de bloqueios na carga de trabalho. O exemplo a seguir mostra como usar uma dica de bloqueio:

    @{lock_scanned_ranges=exclusive}
    SELECT s.SingerId, s.FullName FROM Singers AS s
    JOIN (SELECT SingerId FROM Albums WHERE MarketingBudget > 100000)
    AS a ON a.SingerId = s.SingerId;
    

    Por outro lado, você pode usar a cláusula FOR UPDATE em uma subconsulta, conforme mostrado no exemplo a seguir:

    SELECT s.SingerId, s.FullName FROM Singers AS s
    JOIN (SELECT SingerId FROM Albums WHERE MarketingBudget > 100000)
    FOR UPDATE AS a ON a.SingerId = s.SingerId;
    
  • É possível usar a dica LOCK_SCANNED_RANGES em instruções DML, mas só é possível usar a cláusula FOR UPDATE em instruções SELECT.

Semântica de bloqueio

Para reduzir as solicitações de gravação simultâneas e o custo das transações anuladas como resultado de um deadlock, o Spanner bloqueia os dados no nível da célula, se possível. O nível de célula é o mais granular de dados em uma tabela: um ponto de dados na interseção de uma linha e uma coluna. Ao usar a cláusula FOR UPDATE, o Spanner bloqueia células específicas que são verificadas pela consulta SELECT.

No exemplo a seguir, a célula MarketingBudget na linha SingerId = 1 e AlbumId = 1 é bloqueada exclusivamente na tabela Albums, impedindo que transações simultâneas modifiquem essa célula até que a transação seja confirmada ou revertida. No entanto, transações simultâneas ainda podem atualizar a célula AlbumTitle nessa linha.

SELECT MarketingBudget
FROM Albums
WHERE SingerId = 1 and AlbumId = 1
FOR UPDATE;

Transações simultâneas podem ser bloqueadas ao ler dados bloqueados

Quando uma transação adquire bloqueios exclusivos em um intervalo verificado, transações simultâneas podem bloquear a leitura desses dados. O Spanner impõe a serialização para que os dados só possam ser lidos se houver garantia de que eles não serão alterados por outra transação durante o ciclo de vida da transação. Transações simultâneas que tentam ler dados já bloqueados podem precisar esperar até que a transação que mantém os bloqueios seja confirmada, revertida ou atinja o tempo limite.

No exemplo a seguir, Transaction 1 bloqueia as células MarketingBudget para 1 <= AlbumId < 5.

-- Transaction 1
SELECT MarketingBudget
FROM Albums
WHERE SingerId = 1 and AlbumId >= 1 and AlbumId < 5
FOR UPDATE;

Transaction 2, que está tentando ler o MarketingBudget para AlbumId = 1, fica bloqueado até que Transaction 1 seja confirmado ou revertido.

-- Transaction 2
SELECT MarketingBudget
FROM Albums
WHERE SingerId = 1 and AlbumId = 1;

-- Blocked by Transaction 1

Da mesma forma, uma transação que tenta bloquear um intervalo verificado com FOR UPDATE é bloqueada por uma transação simultânea que bloqueia um intervalo verificado sobreposto.

Transaction 3 no exemplo a seguir também está bloqueado porque Transaction 1 bloqueou as células MarketingBudget para 3 <= AlbumId < 5, que é o intervalo verificado sobreposto com Transaction 3.

-- Transaction 3
SELECT MarketingBudget
FROM Albums
WHERE SingerId = 1 and AlbumId >= 3 and AlbumId < 10
FOR UPDATE;

-- Blocked by Transaction 1

Ler um índice

Uma leitura simultânea pode não ser bloqueada se a consulta que bloqueou o intervalo verificado bloquear as linhas na tabela base, mas a transação simultânea ler de um índice.

O Transaction 1 a seguir bloqueia as células SingerId e SingerInfo para SingerId = 1.

-- Transaction 1
SELECT SingerId, SingerInfo
FROM Singers
WHERE SingerId = 1
FOR UPDATE;

O Transaction 2 somente leitura não é bloqueado pelos bloqueios adquiridos em Transaction 1, porque ele consulta uma tabela de índice.

-- Transaction 2
SELECT SingerId FROM Singers;

Transações simultâneas não bloqueiam operações DML em dados já bloqueados

Quando uma transação adquire bloqueios em um intervalo de células com uma dica de bloqueio exclusivo, as transações simultâneas que tentam realizar uma gravação sem ler os dados primeiro nas células bloqueadas podem continuar. A transação é bloqueada na confirmação até que a transação que mantém os bloqueios seja confirmada ou revertida.

O Transaction 1 a seguir bloqueia as células MarketingBudget para 1 <= AlbumId < 5.

-- Transaction 1
SELECT MarketingBudget
FROM Albums
WHERE SingerId = 1 and AlbumId >= 1 and AlbumId < 5
FOR UPDATE;

Se Transaction 2 tentar atualizar a tabela Albums, isso será bloqueado até que Transaction 1 confirme ou reverta.

-- Transaction 2
UPDATE Albums
SET MarketingBudget = 200000
WHERE SingerId = 1 and AlbumId = 1;

> Query OK, 1 rows affected

COMMIT;

-- Blocked by Transaction 1

As linhas e lacunas atuais são bloqueadas quando um intervalo verificado é bloqueado.

Quando uma transação adquire bloqueios exclusivos em um intervalo verificado, transações simultâneas não podem inserir dados nas lacunas desse intervalo.

O Transaction 1 a seguir bloqueia as células MarketingBudget para 1 <= AlbumId < 10.

-- Transaction 1
SELECT MarketingBudget
FROM Albums
WHERE SingerId = 1 and AlbumId >= 1 and AlbumId < 10
FOR UPDATE;

Se Transaction 2 tentar inserir uma linha para AlbumId = 9 que ainda não existe, isso será bloqueado até que Transaction 1 faça commit ou rollback.

-- Transaction 2
INSERT INTO Albums (SingerId, AlbumId, AlbumTitle, MarketingBudget)
VALUES (1, 9, "Hello hello!", 10000);

> Query OK, 1 rows affected

COMMIT;

-- Blocked by Transaction 1

Advertências de aquisição de bloqueio

A semântica de bloqueio descrita fornece orientação geral, mas não garante exatamente como os bloqueios podem ser adquiridos quando o Spanner executa uma transação que usa a cláusula FOR UPDATE. Os mecanismos de otimização de consultas do Spanner também podem afetar quais bloqueios são adquiridos. A cláusula impede que outras transações modifiquem as células bloqueadas até que a transação atual seja concluída.

Sintaxe das consultas

Esta seção fornece orientações sobre a sintaxe de consulta ao usar a cláusula FOR UPDATE.

O uso mais comum é em uma instrução SELECT de nível superior. Exemplo:

SELECT SingerId, SingerInfo
FROM Singers WHERE SingerID = 5
FOR UPDATE;

Este exemplo mostra como usar a cláusula FOR UPDATE em uma instrução SELECT para bloquear exclusivamente as células SingerId e SingerInfo de WHERE SingerID = 5.

Uso em instruções WITH

A cláusula FOR UPDATE não adquire bloqueios para a instrução WITH quando você especifica FOR UPDATE na consulta de nível externo da instrução WITH.

Na consulta a seguir, nenhum bloqueio é adquirido pela tabela Singers porque a intenção de bloquear não é propagada para a consulta de expressões de tabela comuns (CTE).

WITH s AS (SELECT SingerId, SingerInfo FROM Singers WHERE SingerID > 5)
SELECT * FROM s
FOR UPDATE;

Se a cláusula FOR UPDATE for especificada na consulta de CTE, o intervalo verificado da consulta de CTE vai adquirir os bloqueios.

No exemplo a seguir, as células SingerId e SingerInfo das linhas em que SingerId > 5 estão bloqueadas.

WITH s AS
  (SELECT SingerId, SingerInfo FROM Singers WHERE SingerId > 5 FOR UPDATE)
SELECT * FROM s;

Uso em subconsultas

É possível usar a cláusula FOR UPDATE em uma consulta de nível externo que tenha uma ou mais subconsultas. Os bloqueios são adquiridos pela consulta de nível superior e dentro de subconsultas, exceto em subconsultas de expressão.

A consulta a seguir bloqueia as células SingerId e SingerInfo para linhas em que SingerId > 5.

(SELECT SingerId, SingerInfo FROM Singers WHERE SingerId > 5) AS t
FOR UPDATE;

A consulta a seguir não bloqueia nenhuma célula na tabela Albums porque está em uma subconsulta de expressão. As células SingerId e SingerInfo das linhas retornadas pela subconsulta de expressão são bloqueadas.

SELECT SingerId, SingerInfo
FROM Singers
WHERE SingerId = (SELECT SingerId FROM Albums WHERE MarketingBudget > 100000)
FOR UPDATE;

Usado para consultar visualizações

Você pode usar a cláusula FOR UPDATE para consultar uma visualização, conforme mostrado no exemplo a seguir:

CREATE VIEW SingerBio AS SELECT SingerId, FullName, SingerInfo FROM Singers;

SELECT * FROM SingerBio WHERE SingerId = 5 FOR UPDATE;

Não é possível usar a cláusula FOR UPDATE ao definir uma visualização.

Casos de uso não aceitos

Os seguintes casos de uso do FOR UPDATE não são compatíveis:

  • Como um mecanismo de exclusão mútua para execução de código fora do Spanner:não use o bloqueio no Spanner para garantir acesso exclusivo a um recurso fora dele. As transações podem ser canceladas pelo Spanner, por exemplo, se uma transação for repetida, seja explicitamente pelo código do aplicativo ou implicitamente pelo código do cliente, como o driver JDBC do Spanner, só é garantido que os bloqueios sejam mantidos durante a tentativa que foi executada.
  • Em combinação com a dica LOCK_SCANNED_RANGES:não é possível usar a cláusula FOR UPDATE e a dica LOCK_SCANNED_RANGES na mesma consulta. Caso contrário, o Spanner vai retornar um erro.
  • Em consultas de pesquisa de texto completo:não é possível usar a cláusula FOR UPDATE em consultas que usam índices de pesquisa de texto completo.
  • Em transações somente leitura:a cláusula FOR UPDATE só é válida em consultas executadas em transações de leitura e gravação.
  • Em instruções DDL:não é possível usar a cláusula FOR UPDATE em consultas dentro de instruções DDL, que são armazenadas para execução posterior. Por exemplo, não é possível usar a cláusula FOR UPDATE ao definir uma visualização. Se o bloqueio for necessário, a cláusula FOR UPDATE poderá ser especificada ao consultar a visualização.

A seguir