Usar SELECT FOR UPDATE

Esta página descreve como usar a cláusula FOR UPDATE no Spanner.

Ao usar a consulta SELECT para verificar uma tabela, adicione uma cláusula FOR UPDATE para ativar bloqueios exclusivos no 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 duração da 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 mais, 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 rígidos, a cláusula FOR UPDATE pode ser necessária para garantir que uma transação simultânea não atualize dados entre a leitura dos dados e a confirmação da transação. Como o Spanner sempre aplica a serializabilidade, é garantido que a transação só será confirmada se os dados acessados na transação 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 leem e gravam simultaneamente nos mesmos dados, as transações simultâneas podem causar um aumento nos cancelamentos. Isso ocorre porque, quando várias transações simultâneas adquirem bloqueios compartilhados e tentam fazer upgrade para bloqueios exclusivos, as transações causam um impasse. O Spanner, em seguida, cancela todas as transações, exceto uma. Para mais informações, consulte Bloqueio.

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

Portanto, se reduzir o número de transações abortadas em um cenário de solicitação de gravação simultânea for importante, use a cláusula FOR UPDATE para reduzir o número geral de abortos 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 travas exclusivas em uma subconsulta. O uso da dica de bloqueio pode resultar na aquisição de mais bloqueios do que o necessário e contribuir para a contenção de bloqueio 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, é possível usar a cláusula FOR UPDATE em uma subconsulta, conforme mostrado neste exemplo:

    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 sugestão LOCK_SCANNED_RANGES em instruções DML, mas apenas a cláusula FOR UPDATE pode ser usada em instruções SELECT.

Semântica de bloqueio

Para reduzir as solicitações de gravação simultâneas e o custo das transações abortadas como resultado de um deadlock, o Spanner bloqueia os dados no nível da célula, se possível. Ao usar a cláusula FOR UPDATE, o Spanner bloqueia células específicas que são verificadas pela consulta SELECT.

No exemplo abaixo, a célula MarketingBudget nas linhas 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, as 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 bloquear a leitura de 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 aplica a serializabilidade para que os dados só possam ser lidos se for garantido que não foram alterados por outra transação durante o ciclo de vida dela. 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 ou revertida.

No exemplo abaixo, 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;

O Transaction 2, que está tentando ler o MarketingBudget para AlbumId = 1, fica bloqueado até que o 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 digitalizado com FOR UPDATE é bloqueada por uma transação simultânea que bloqueia um intervalo digitalizado sobreposto.

O 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 de verificação 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 consulta uma tabela de índice.

-- Transaction 2
SELECT SingerId FROM Singers;

As transações simultâneas não bloqueiam as 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 prosseguir. A transação bloqueia no commit 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, ela será bloqueada até que Transaction 1 seja confirmada ou revertida.

-- 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 existentes são bloqueadas quando um intervalo digitalizado é bloqueado

Quando uma transação adquire bloqueios exclusivos em um intervalo digitalizado, as 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, ela será bloqueada até que Transaction 1 seja confirmada ou revertida.

-- 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ções gerais, 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 consulta 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.

Semântica da consulta

Esta seção fornece orientações sobre a semântica da consulta ao usar a cláusula FOR UPDATE.

Usar 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, nenhuma trava é adquirida pela tabela Singers porque a intenção de bloquear não é propagada para a consulta de expressões de tabela comum (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 CTE, o intervalo verificado da consulta CTE vai adquirir as travas.

No exemplo abaixo, 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á dentro de 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;

Usar para consultar visualizações

É possível usar a cláusula FOR UPDATE para consultar uma visualização, conforme mostrado no exemplo abaixo:

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 de FOR UPDATE não têm suporte:

  • Como um mecanismo de exclusão mútua para executar código fora do Spanner:não use o bloqueio no Spanner para garantir acesso exclusivo a um recurso fora do Spanner. As transações podem ser abortadas 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 são mantidos durante a tentativa que foi executada.
  • Em combinação com a sugestão LOCK_SCANNED_RANGES:não é possível usar a cláusula FOR UPDATE e a sugestão 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 em 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