Use SELECT FOR UPDATE

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

Quando usa a consulta SELECT para analisar uma tabela, adicione uma cláusula FOR UPDATE para ativar bloqueios exclusivos ao nível de granularidade de linhas e colunas, também conhecido como nível de célula. O bloqueio permanece em vigor durante toda a transação de leitura/escrita. Durante este 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 motivo deve usar a cláusula FOR UPDATE

Em bases 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 atualiza os dados entre a leitura dos dados e a confirmação da transação. Uma vez que o Spanner aplica sempre a serialização, é garantido que a transação só é confirmada com êxito se os dados acedidos na transação não estiverem desatualizados no momento da confirmação. Por conseguinte, a cláusula FOR UPDATE não é necessária para garantir a correção das transações no Spanner.

No entanto, em exemplos de utilização com elevada contenção de escrita, como quando várias transações estão a ler e a escrever simultaneamente nos mesmos dados, as transações simultâneas podem causar um aumento nas anulações. Isto acontece porque, quando várias transações simultâneas adquirem bloqueios partilhados e, em seguida, tentam atualizar para bloqueios exclusivos, as transações causam um impasse. Em seguida, o Spanner anula todas as transações, exceto uma. Para mais informações, consulte a secção Bloquear.

Uma transação que usa a cláusula FOR UPDATE adquire o bloqueio exclusivo e continua a ser executada, enquanto outras transações aguardam a sua vez para o bloqueio. Embora o Spanner possa continuar a limitar a taxa de transferência porque as transações em conflito só podem ser realizadas uma de cada vez, o Spanner só faz progressos numa transação, o que poupa tempo que, de outra forma, seria gasto a anular e a tentar novamente as transações.

Por conseguinte, se for importante reduzir o número de transações anuladas num cenário de pedido de gravação simultâneo, pode usar 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 sugestão LOCK_SCANNED_RANGES

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

Existem duas diferenças principais:

  • Se usar a sugestão LOCK_SCANNED_RANGES, a transação adquire bloqueios exclusivos nos intervalos analisados para toda a declaração. Não pode adquirir bloqueios exclusivos numa subconsulta. A utilização da sugestão de bloqueio pode resultar na aquisição de mais bloqueios do que o necessário e contribuir para a contenção de bloqueios na carga de trabalho. O exemplo seguinte mostra como usar uma sugestão 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, pode usar a cláusula FOR UPDATE numa subconsulta, conforme mostrado no seguinte 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;
    
  • Pode usar a sugestão LOCK_SCANNED_RANGES em declarações DML, enquanto só pode usar a cláusula FOR UPDATE em declarações SELECT.

Semântica de bloqueio

Para reduzir os pedidos de escrita simultâneos e o custo das transações anuladas devido a um impasse, o Spanner bloqueia os dados ao nível da célula, se possível. Quando usa a cláusula FOR UPDATE, o Spanner bloqueia células específicas que são analisadas pela consulta SELECT.

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

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

As transações simultâneas podem bloquear a leitura de dados bloqueados

Quando uma transação adquire bloqueios exclusivos num intervalo analisado, as transações simultâneas podem bloquear a leitura desses dados. O Spanner aplica a serialização para que os dados só possam ser lidos se for garantido que não foram alterados por outra transação durante a duração da transação. As transações simultâneas que tentam ler dados já bloqueados podem ter de esperar até que a transação que detém os bloqueios seja confirmada ou revertida.

No exemplo seguinte, 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á a tentar ler o MarketingBudget para AlbumId = 1, está bloqueado até que Transaction 1 confirme ou seja revertido.

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

-- Blocked by Transaction 1

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

Transaction 3 no exemplo seguinte também está bloqueado, uma vez que Transaction 1 bloqueou as células MarketingBudget para 3 <= AlbumId < 5, que é o intervalo analisado 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

Leia um índice

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

O seguinte Transaction 1 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 de leitura não está 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 num intervalo de células com uma sugestão de bloqueio exclusiva, as transações simultâneas que tentam executar uma gravação sem ler primeiro os dados nas células bloqueadas podem prosseguir. A transação é bloqueada no commit até que a transação que detém os bloqueios seja confirmada ou revertida.

O seguinte 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;

Se Transaction 2 tentar atualizar a tabela Albums, a ação é bloqueada 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 os espaços existentes são bloqueados quando um intervalo analisado é bloqueado

Quando uma transação adquiriu bloqueios exclusivos num intervalo analisado, as transações concorrentes não podem inserir dados nas lacunas desse intervalo.

O seguinte Transaction 1 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, é impedido de o fazer até que Transaction 1 confirme ou reverta.

-- 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

Restrições de aquisição de bloqueios

A semântica de bloqueio descrita fornece orientações gerais, mas não é uma garantia sobre a forma exata 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 os bloqueios 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 de consulta

Esta secção fornece orientações sobre a semântica de consulta quando usa a cláusula FOR UPDATE.

Use em declarações WITH

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

Na consulta seguinte, a tabela Singers não adquire bloqueios porque a intenção de bloquear não é propagada para a consulta de expressões de tabelas 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 CTE, o intervalo analisado da consulta CTE adquire os bloqueios.

No exemplo seguinte, as células SingerId e SingerInfo para as linhas onde SingerId > 5 estão bloqueadas.

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

Use em subconsultas

Pode usar a cláusula FOR UPDATE numa consulta de nível exterior que tenha uma ou mais subconsultas. Os bloqueios são adquiridos pela consulta de nível superior e nas subconsultas, exceto nas subconsultas de expressões.

A seguinte consulta bloqueia as células SingerId e SingerInfo para as linhas onde SingerId > 5.

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

A consulta seguinte não bloqueia nenhuma célula na tabela Albums porque está numa subconsulta de expressão. As células SingerId e SingerInfo das linhas devolvidas pela subconsulta de expressão estão bloqueadas.

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

Use para consultar vistas

Pode usar a cláusula FOR UPDATE para consultar uma visualização, conforme mostrado no seguinte exemplo:

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

SELECT * FROM SingerBio WHERE SingerId = 5 FOR UPDATE;

Não pode usar a cláusula FOR UPDATE quando define uma vista.

Exemplos de utilização não suportados

Os seguintes exemplos de utilização FOR UPDATE não são suportados:

  • Como um mecanismo de exclusão mútua para executar código fora do Spanner: não use o bloqueio no Spanner para garantir o acesso exclusivo a um recurso fora do Spanner. As transações podem ser anuladas pelo Spanner, por exemplo, se uma transação for repetida, quer seja explicitamente pelo código da aplicação ou implicitamente pelo código do cliente, como o controlador JDBC do Spanner, só é garantido que os bloqueios são mantidos durante a tentativa que foi confirmada.
  • Em combinação com a sugestão LOCK_SCANNED_RANGES: não pode usar a cláusula FOR UPDATE e a sugestão LOCK_SCANNED_RANGES na mesma consulta. Caso contrário, o Spanner devolve um erro.
  • Em consultas de pesquisa de texto completo: não pode usar a cláusula FOR UPDATE em consultas que usam índices de pesquisa de texto completo.
  • Em transações só de leitura: a cláusula FOR UPDATE só é válida em consultas executadas em transações de leitura/escrita.
  • Em declarações DDL: não pode usar a cláusula FOR UPDATE em consultas em declarações DDL, que são armazenadas para execução posterior. Por exemplo, não pode usar a cláusula FOR UPDATE quando define uma vista. Se o bloqueio for necessário, a cláusula FOR UPDATE pode ser especificada ao consultar a vista.

O que se segue