Esta página foi traduzida pela API Cloud Translation.
Switch to English

Estatísticas de bloqueio

O Cloud Spanner fornece estatísticas de bloqueio que permitem identificar a chave de linha e as colunas da tabela que foram as principais fontes de conflitos de bloqueio de transação no banco de dados durante um determinado período. É possível recuperar essas estatísticas das tabelas do sistema SPANNER_SYS.LOCK_STATS* usando instruções SQL.

Disponibilidade

SPANNER_SYS os dados estão disponíveis somente por meio de interfaces SQL (por exemplo, executeQuery e gcloud spanner databases execute-sql); outros métodos de leitura única fornecidos pelo Cloud Spanner não são compatíveis com SPANNER_SYS.

Bloquear estatísticas por chave de linha

As tabelas a seguir rastreiam a chave de linha com o maior tempo de espera:

  • SPANNER_SYS.LOCK_STATS_TOP_MINUTE: chaves de linha com os maiores tempos de espera de bloqueio durante intervalos de 1 minuto.

  • SPANNER_SYS.LOCK_STATS_TOP_10MINUTE: chaves de linha com os maiores tempos de espera de bloqueio durante intervalos de 10 minutos.

  • SPANNER_SYS.LOCK_STATS_TOP_HOUR: chaves de linha com os maiores tempos de espera de bloqueio durante intervalos de 1 hora

Essas tabelas têm as seguintes propriedades:

  • Cada uma contém dados para intervalos de tempo não sobrepostos do comprimento que o nome da tabela específica.

  • Os intervalos são baseados em horas. Os intervalos de 1 minuto terminam no minuto, os de 10 minutos terminam a cada 10 minutos, começando na hora, e os intervalos de 1 hora terminam na hora. Após cada intervalo, o Cloud Spanner coleta dados de todos os servidores e disponibiliza os dados nas tabelas SPANNER_SYS pouco tempo depois.

    Por exemplo, às 11:59:30, os intervalos mais recentes disponíveis para as consultas SQL são:

    • 1 minuto: 11:58:00–11:58:59
    • 10 minutos: 11:40:00–11:49:59
    • 1 hora: 10:00:00–10:59:59
  • O Cloud Spanner agrupa as estatísticas iniciando o intervalo de chaves de linha.

  • Cada linha contém estatísticas para o tempo de espera de bloqueio total de um determinado intervalo de chaves de linha inicial para o qual o Cloud Spanner captura estatísticas durante o intervalo especificado.

  • Se o Cloud Spanner não puder armazenar informações sobre cada intervalo de chaves de linha para espera de bloqueio durante o intervalo, o sistema priorizará o intervalo de chaves de linha com o maior tempo de espera de bloqueio durante o intervalo especificado.

Esquema de tabela

Nome da coluna Tipo Descrição
INTERVAL_END TIMESTAMP Fim do intervalo de tempo em que os conflitos de bloqueio incluídos ocorreram.
ROW_RANGE_START_KEY BYTES(MAX) A chave de linha em que ocorreu o conflito de bloqueio. Quando o conflito envolve um intervalo de linhas, esse valor representa a chave inicial desse intervalo. Um sinal de adição, +, significa um intervalo. Para mais informações, consulte O que é uma chave inicial de intervalo de linhas.
LOCK_WAIT_SECONDS FLOAT64 O tempo de espera de bloqueio cumulativo registrado sobre todas as colunas no intervalo de chaves de linha, em segundos.
SAMPLE_LOCK_REQUESTS ARRAY<STRUCT<
  lock_mode STRING,
  column STRING>>
Cada entrada desta matriz corresponde a uma solicitação de bloqueio de amostra que contribuiu para o conflito de bloqueio na chave de linha especificada ou no intervalo de chaves de linha. O número máximo de amostras nesta matriz é 20. Cada amostra contém os dois campos a seguir:

  • lock_mode: o modo de bloqueio que foi solicitado. Para mais informações, consulte Modos de bloqueio
  • column: a coluna que encontrou o conflito de bloqueio. O formato desse valor é tablename.columnname.

Modos de bloqueio

As operações do Cloud Spanner adquirem bloqueios quando as operações fazem parte de uma transação de leitura e gravação. Transações somente leitura não adquirem bloqueios. O Cloud Spanner usa diferentes modos de bloqueio para maximizar o número de transações que têm acesso a uma determinada célula de dados em um determinado momento. Bloqueios diferentes têm características diferentes. Por exemplo, alguns bloqueios podem ser compartilhados entre várias transações, enquanto outros não.

Um conflito de bloqueios pode ocorrer quando você tenta adquirir um dos seguintes modos de bloqueio em uma transação.

  • ReaderShared Bloquear: um bloqueio que permite que outras leituras ainda acessem os dados até que a transação esteja pronta para confirmação. Esse bloqueio compartilhado é adquirido quando uma transação de leitura e gravação lê dados.

  • WriterShared Bloquear: esse bloqueio é adquirido quando uma transação de leitura e gravação tenta confirmar uma gravação.

  • ExclusiveBloqueio: um bloqueio exclusivo é adquirido quando uma transação de leitura e gravação, que já adquiriu um bloqueio ReaderShared, tenta gravar dados após a conclusão da leitura. Um bloqueio exclusivo é um upgrade de um bloqueio ReaderShared. Um bloqueio exclusivo é um caso especial de uma transação que mantém os bloqueios do ReaderShared e do WriterShared ao mesmo tempo. Nenhuma outra transação pode adquirir qualquer bloqueio na mesma célula.

  • WriterSharedTimestamp Lock: um tipo especial de bloqueio WriterShared que é adquirido ao inserir novas linhas em uma tabela que tem um carimbo de data/hora de confirmação como parte da chave primária. Esse tipo de bloqueio impede que os participantes da transação criem exatamente a mesma linha e, portanto, entrem em conflito entre si. O Cloud Spanner atualiza a chave da linha inserida para corresponder ao carimbo de data/hora de confirmação da transação que executou a inserção.

Para mais informações sobre os tipos de transação e os tipos de bloqueios que estão disponíveis, consulte Transações.

Conflitos no modo de bloqueio

A tabela a seguir mostra os possíveis conflitos entre diferentes modos de bloqueio.

Modos de bloqueio Leitor compartilhado WriterShared Exclusivo WriterSharedTimestamp
Leitor compartilhado Não Sim Sim Sim
WriterShared Sim Não Sim Não relevante
Exclusivo Sim Sim Sim Não relevante
WriterSharedTimestamp Sim Não relevante Não relevante Sim

Os bloqueios de WriterSharedTimestamp são usados apenas ao inserir novas linhas com um carimbo de data/hora como parte de sua chave primária. Os bloqueios WriterShared e Exclusive são usados ao gravar em células atuais ou inserir novas linhas sem carimbos de data/hora. Como resultado, WriterSharedTimestamp não pode entrar em conflito com outros tipos de bloqueios e esses cenários são mostrados como Não aplicável na tabela anterior.

A única exceção é ReaderShared, que pode ser aplicada a linhas não existentes e, portanto, pode entrar em conflito com WriterSharedTimestamp. Por exemplo, uma verificação completa da tabela bloqueia a tabela inteira mesmo para linhas que não foram criadas. Por isso, é possível que ReaderShared entre em conflito com WriterSharedTimestamp.

O que é uma chave inicial de intervalo de linhas?

A coluna ROW_RANGE_START_KEY identifica a chave primária composta ou inicia a chave primária de um intervalo de linhas, que tem conflitos de bloqueio. O esquema a seguir é usado para ilustrar um exemplo.

CREATE TABLE Singers (
  SingerId   INT64 NOT NULL,
  FirstName  STRING(1024),
  LastName   STRING(1024),
  SingerInfo BYTES(MAX),
) PRIMARY KEY (SingerId);

CREATE TABLE Albums (
  SingerId     INT64 NOT NULL,
  AlbumId      INT64 NOT NULL,
  AlbumTitle   STRING(MAX),
) PRIMARY KEY (SingerId, AlbumId),
  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

CREATE TABLE Songs (
  SingerId     INT64 NOT NULL,
  AlbumId      INT64 NOT NULL,
  TrackId      INT64 NOT NULL,
  SongName     STRING(MAX),
) PRIMARY KEY (SingerId, AlbumId, TrackId),
  INTERLEAVE IN PARENT Albums ON DELETE CASCADE;

CREATE TABLE Users (
  UserId     INT64 NOT NULL,
  LastAccess TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true),
  ...
) PRIMARY KEY (UserId, LastAccess);

Conforme mostrado na tabela a seguir dos intervalos de chaves de chave e de linha, um intervalo é representado por um sinal de adição "+"; A chave nesses casos representa a chave inicial de um intervalo de chaves em que ocorreu um conflito de bloqueio.

ROW_RANGE_START_KEY Explicação
cantores(2) Tabela "Singers" na chave SingerId=2
álbuns(2,1) Albums a partir da chave SingerId=2,AlbumId=1
músicas(2,1,5) Tabela de músicas na chave SingerId=2,AlbumId=1,TrackId=5
músicas(2,1,5+) Intervalo de chave da tabela de músicas a partir de SingerId=2,AlbumId=1,TrackId=5
álbuns(2,1+) Intervalo de chaves da tabela de álbuns a partir de SingerId=2,AlbumId=1
users(3, 2020-11-01 12:34:56.426426+00:00) Tabela de usuários na chave UserId=3, LastAccess=commit_timestamp

Agregar estatísticas

SPANNER_SYS também contém tabelas para armazenar dados agregados de estatísticas de bloqueio capturadas pelo Cloud Spanner em um período específico:

  • SPANNER_SYS.LOCK_STATS_TOTAL_MINUTE: agrega estatísticas de todas as esperas de bloqueio durante intervalos de 1 minuto.

  • SPANNER_SYS.LOCK_STATS_TOTAL_10MINUTE: agrega estatísticas de todos os bloqueios durante os intervalos de 10 minutos.

  • SPANNER_SYS.LOCK_STATS_TOTAL_HOUR: agrega estatísticas de todas as esperas de bloqueio durante intervalos de 1 hora.

As tabelas de estatísticas agregadas têm as seguintes propriedades:

  • Cada uma contém dados para intervalos de tempo não sobrepostos do comprimento que o nome da tabela específica.

  • Os intervalos são baseados em tempos de relógio. Os de 1 minuto terminam no minuto, os de 10 minutos terminam a cada 10 minutos, começando na hora, e os intervalos de 1 hora terminam na hora.

    Por exemplo, às 23h59, os intervalos mais recentes disponíveis para consultas SQL em estatísticas de bloqueio agregado são:

    • 1 minuto: 11:58:00–11:58:59
    • 10 minutos: 11:40:00–11:49:59
    • 1 hora: 10:00:00–10:59:59
  • Cada linha contém estatísticas para todas as esperas de bloqueio no banco de dados durante o intervalo especificado, agregadas juntas. Há apenas uma linha por intervalo de tempo.

  • As estatísticas capturadas nas tabelas SPANNER_SYS.LOCK_STATS_TOTAL_* incluem bloqueios de bloqueio que o Cloud Spanner não captura nas tabelas SPANNER_SYS.LOCK_STATS_TOP_*.

Esquema de tabela

Nome da coluna Tipo Descrição
INTERVAL_END TIMESTAMP Fim do intervalo de tempo em que o conflito de bloqueio ocorreu.
TOTAL_LOCK_WAIT_SECONDS FLOAT64 Tempo de espera total de bloqueio para conflitos de bloqueio registrados para todo o banco de dados, em segundos.

Exemplo de consultas

Veja a seguir um exemplo de instrução SQL que pode ser usada para recuperar estatísticas de bloqueio. É possível executar essas instruções SQL usando as bibliotecas de cliente, a ferramenta de linha de comando gcloud ou o Console do Cloud.

Como listar as estatísticas de bloqueio do intervalo de 1 minuto anterior

A consulta a seguir retorna as informações de espera de bloqueio para cada chave de linha com um conflito de bloqueio, incluindo a fração do total de conflitos de bloqueio, durante o intervalo de tempo de um minuto mais recente.

A função CAST() converte o campo row_range_start_key BYTES em uma STRING.

SELECT CAST(s.row_range_start_key AS STRING) AS row_range_start_key,
       t.total_lock_wait_seconds,
       s.lock_wait_seconds,
       s.lock_wait_seconds/t.total_lock_wait_seconds frac_of_total,
       s.sample_lock_requests
FROM spanner_sys.lock_stats_total_minute t, spanner_sys.lock_stats_top_minute s
WHERE t.interval_end =
  (SELECT MAX(interval_end)
   FROM spanner_sys.lock_stats_total_minute)
AND s.interval_end = t.interval_end
ORDER BY s.lock_wait_seconds DESC;
Saída da consulta
linha_intervalo_de_início total_lock_wait_segundos lock_wait_seconds franco_de_total sample_lock_requests (em inglês)
Músicas(2,1,1) 0,0,1847 0,0,1847 1 LOCK_MODE: ReaderShared

COLUMN: Singers.SingerInfo

LOCK_MODE: WriterShared

COLUMN: Singers.SingerInfo
Users(3, 2020-11-01 12:34:56.426426+00:00) 5,21 2,37 0,54 LOCK_MODE: ReaderShared

COLUMN: users._exists1

LOCK_MODE: WriterShared

COLUMN: users._exists1

1 _exists é um campo interno usado para verificar se determinada linha existe ou não.

Retenção de dados

O Cloud Spanner mantém dados para cada tabela, no mínimo, pelos períodos a seguir:

  • SPANNER_SYS.LOCK_STATS_TOP_MINUTE e SPANNER_SYS.LOCK_STATS_TOTAL_MINUTE: intervalos que abrangem as seis horas anteriores.

  • SPANNER_SYS.LOCK_STATS_TOP_10MINUTE e SPANNER_SYS.LOCK_STATS_TOTAL_10MINUTE: intervalos abrangendo os quatro dias anteriores.

  • SPANNER_SYS.LOCK_STATS_TOP_HOUR e SPANNER_SYS.LOCK_STATS_TOTAL_HOUR: intervalos que abrangem os últimos 30 dias.

Como solucionar conflitos de bloqueio no seu banco de dados com o uso de estatísticas de bloqueio

No procedimento a seguir, mostraremos como usar estatísticas de bloqueio para investigar conflitos de bloqueio no banco de dados.

Imagine que você ouça reclamações de que o aplicativo da sua empresa está lento. Siga as etapas abaixo para investigar o problema.

Como selecionar um período a ser investigado

Examine as métricas de latência do seu banco de dados do Cloud Spanner e descubra um período em que seu aplicativo apresenta alta latência e uso da CPU. Por exemplo, digamos que o problema começou a acontecer por volta das 22h50 em 12 de novembro de 2020.

Como determinar se a latência de confirmação de transação aumentou, juntamente com o tempo de espera de bloqueio durante o período selecionado.

Os bloqueios são adquiridos por transações. Portanto, se os conflitos de bloqueio causarem longos tempos de espera, poderemos ver o aumento na latência de confirmação da transação com o aumento no tempo de espera de bloqueio.

Depois de selecionar um período para iniciar nossa investigação, mostraremos as estatísticas de transações TXN_STATS_TOTAL_10MINUTE às estatísticas de bloqueio LOCK_STATS_TOTAL_10MINUTE ao longo do tempo para nos ajudar. entender se o aumento da latência média de confirmação é contribuindo pelo aumento do tempo de espera de bloqueio.

SELECT t.interval_end, t.avg_commit_latency_seconds, l.total_lock_wait_seconds
FROM spanner_sys.txn_stats_total_10minute t
LEFT JOIN spanner_sys.lock_stats_total_10minute l
ON t.interval_end = l.interval_end
WHERE
  t.interval_end >= "2020-11-12T21:50:00Z"
  AND t.interval_end <= "2020-11-12T23:50:00Z"
ORDER BY interval_end;

Use os dados a seguir como um exemplo dos resultados que recebemos de nossa consulta.

interval_end avg_commit_latency_seconds total_lock_wait_segundos
12-11-2019 21:40:00-07:00 0,002 0,090
12/11/2020:00:00-07:00 0,003 0,110
12/11-2 22:00:00-07:00 0,002 0,100
12/11-2 22:10:00-07:00 0,002 0,080
12/11-2 22:20-07:00 0,030 0,240
12/11/2020 22:00-07:00 0,034 0,220
12-11-2019 22:40:00-07:00 0,034 0,218
202:10-12 22:50:00-07:00 3,741 780,193
12/11-2020-07:00 0,042 0,240
12-11-2019 23:10:00-07:00 0,038 0,129
12/11/2020 23:00-07:00 0,021 0,128
12/11/2020 23:00-07:00 0,038 0,23

Esses resultados anteriores mostram um aumento dramático em avg_commit_latency_seconds e total_lock_wait_seconds durante o mesmo período de 202:10-12 22:40:00 a Após 12/11 2020, às 22h50m00s. Observe que avg_commit_latency_seconds é o tempo médio gasto apenas na etapa de confirmação. Por outro lado, total_lock_wait_seconds é o tempo de bloqueio agregado para o período. Portanto, o tempo parece muito maior que o tempo de confirmação da transação.

Agora que já confirmou que o tempo de espera de bloqueio está intimamente relacionado ao aumento na latência de gravação, investigaremos na próxima etapa quais linhas e colunas geram a espera longa.

Descobrir quais chaves e colunas de linhas tinham tempos de espera de bloqueio longos durante o período selecionado

Para descobrir quais chaves de linha e colunas tiveram os tempos de espera de bloqueio altos durante o período que estamos investigando, consultemos oLOCK_STAT_TOP_10MINUTE, que lista as chaves de linha e colunas que contribuem mais para o bloqueio.

A função CAST() na consulta a seguir converte o campo row_range_start_key BYTES em uma STRING.

SELECT CAST(s.row_range_start_key AS STRING) AS row_range_start_key,
       t.total_lock_wait_seconds,
       s.lock_wait_seconds,
       s.lock_wait_seconds/t.total_lock_wait_seconds frac_of_total,
       s.sample_lock_requests
FROM spanner_sys.lock_stats_total_10minute t, spanner_sys.lock_stats_top_10minute s
WHERE
  t.interval_end = "2020-11-12T22:50:00Z" and s.interval_end = t.interval_end;
linha_intervalo_de_início total_lock_wait_segundos lock_wait_seconds franco_de_total sample_lock_requests (em inglês)
Singers(32) 780,193 780,193 1 LOCK_MODE: WriterShared

COLUMN: Singers.SingerInfo

LOCK_MODE: ReaderShared

COLUMN: Singers.SingerInfo

Nessa tabela de resultados, vemos o conflito acontecendo na tabela Singers, na chave SingerId=32. Singers.SingerInfo é a coluna em que o conflito de bloqueio aconteceu entre o ReaderShared e o WriterShared.

Esse é um tipo comum de conflito quando há uma transação tentando ler uma determinada célula e a outra transação está tentando gravar na mesma célula. Sabemos agora a célula de dados exata para que as transações disputam o bloqueio. Por isso, na próxima etapa, identificaremos as transações que estão disputando os bloqueios.

Como descobrir quais transações acessam as colunas envolvidas no conflito de bloqueio

Para descobrir quais transações tentam ler ou gravar as colunas envolvidas no conflito de bloqueio, unimos as estatísticas de transações às estatísticas de bloqueio na consulta a seguir.

Podemos identificar as transações recuperando fprint, read_columns, write_constructive_columns e avg_commit_latency_seconds de TXN_STATS_TOTAL_10MINUTE.

SELECT
  fprint,
  read_columns,
  write_constructive_columns,
  avg_commit_latency_seconds
FROM spanner_sys.txn_stats_top_10minute t2
WHERE (
  EXISTS (
    SELECT * FROM t2.read_columns columns WHERE columns IN (
      SELECT DISTINCT(req.COLUMN)
      FROM spanner_sys.lock_stats_top_10minute t, t.SAMPLE_LOCK_REQUESTS req
      WHERE req.LOCK_MODE = "ReaderShared" AND t.interval_end ="2020-11-12T23:50:00Z"))
OR
  EXISTS (
    SELECT * FROM t2.write_constructive_columns columns WHERE columns IN (
      SELECT DISTINCT(req.COLUMN)
      FROM spanner_sys.lock_stats_top_10minute t, t.SAMPLE_LOCK_REQUESTS req
      WHERE req.LOCK_MODE = "WriterShared" AND t.interval_end ="2020-11-12T23:50:00Z"))
)
AND t2.interval_end ="2020-11-12T23:50:00Z"
ORDER BY avg_commit_latency_seconds DESC;

A consulta identifica as transações a seguir no período que estamos investigando.

  • As transações que leem qualquer uma das colunas que tiveram um conflito de bloqueio ao tentar adquirir o bloqueio ReaderShared.

  • Transações que gravam em qualquer uma das colunas que tiveram um conflito de bloqueio ao tentar adquirir um bloqueio WriterShared.

O resultado da consulta é classificado pela coluna avg_commit_latency_seconds. Portanto, a transação possivelmente afetada pela espera de bloqueio é mais retornada.

fprint read_columns write_constructive_columns avg_commit_latency_seconds
1866043996151916800


['Singers.SingerInfo',
'Singers.FirstName',
'Singers.LastName',
'Singers._exists'] (em inglês)
['Singers.SingerInfo'] 4,89
4168578515815911936 [] ['Singers.SingerInfo'] 3,65

Como os resultados da consulta são exibidos, duas transações tentaram acessar Singers.SingerInfo, que é a coluna que teve conflitos de bloqueio durante o período. Depois que as transações que causam os conflitos de bloqueio forem identificadas, você poderá continuar a analisar as transações usando a impressão digital, fprint, para identificar possíveis problemas que contribuíram para o problema.

Depois de analisar a transação com fprint=1866043996151916800, descobrimos que a DML na transação em questão não é filtrada na chave primária, SingerId. Portanto, ele causou uma verificação completa da tabela e a bloqueou até que a transação fosse confirmada. Se soubermos SingerId, podemos atualizar a cláusula WHERE na instrução para a condição em SingerId. Se não soubermos os códigos das linhas a serem atualizadas, poderemos usar uma transação somente leitura separada para buscar os códigos e, em seguida, enviar outra transação de leitura e gravação para atualizar o linhas com base nos IDs.

Como aplicar as práticas recomendadas para reduzir a contenção de bloqueio

Em nosso cenário de exemplo, conseguimos usar estatísticas de bloqueio e estatísticas de transação para restringir nosso problema a uma transação que não estava usando a chave primária da nossa tabela ao fazer atualizações. Trabalhamos com ideias para melhorar a transação com base no fato de sabermos sobre as chaves das linhas que queríamos atualizar antes ou não.

Ao analisar possíveis problemas na solução ou até mesmo ao projetar a solução, considere estas práticas recomendadas para reduzir o número de conflitos de bloqueio no banco de dados.

  • Use transações somente leitura sempre que possível, porque elas não adquirem bloqueios.

  • Evite verificações completas de tabelas em uma transação de leitura e gravação. Isso inclui gravar uma condicional DML na chave primária ou atribuir um intervalo de chaves específico ao usar a API Read.

  • Mantenha o período de bloqueio curto confirmando a alteração assim que você ler os dados possível em uma transação de leitura e gravação. Uma transação de leitura e gravação garante que os dados permaneçam inalterados depois que você ler os dados até confirmar a alteração. Para isso, a transação requer o bloqueio das células de dados durante a leitura e durante a confirmação. Como resultado, se você mantiver o período de bloqueio curto, as transações terão menos probabilidade de ter conflitos de bloqueio.

  • Prefira transações pequenas em transações grandes ou considere a DML particionada para transações de DML de longa duração. Uma transação de longa duração adquire um bloqueio por muito tempo, portanto, considere dividir uma transação que aproxime milhares de linhas em várias transações menores, que atualizam centenas de linhas sempre que possível.

  • Caso não precise da garantia fornecida por uma transação de leitura e gravação, evite ler os dados na transação de leitura e gravação antes de confirmar a alteração, por exemplo, lendo os dados em uma leitura somente leitura separada. transação. A maioria dos conflitos de bloqueio ocorre devido à grande garantia, para garantir que os dados permaneçam inalterados entre a leitura e a confirmação. Então, se a transação de leitura e gravação não ler dados, ela não precisará bloquear as células por muito tempo.

  • Siga as práticas recomendadas de design de esquema.

A seguir