As inconsistências nos índices da base de dados podem ocorrer por vários motivos, incluindo defeitos de software, problemas de hardware ou alterações subjacentes no comportamento, como alterações na ordem de ordenação.
A comunidade PostgreSQL criou ferramentas para identificar e corrigir esses problemas.
Isto inclui ferramentas como o amcheck
, que é recomendado pela comunidade do PostgreSQL para identificar problemas de consistência, incluindo problemas que as versões anteriores do PostgreSQL 14 apresentavam.
Este manual é uma referência para os utilizadores do AlloyDB for PostgreSQL que têm estes problemas de consistência. O manual fornece informações que ajudam os utilizadores do PostgreSQL a identificar e corrigir índices inconsistentes.
A resolução das inconsistências de um índice envolve os seguintes passos:
-
Antes de começar a reindexar, deve fazer uma cópia de segurança da base de dados, definir as autorizações corretas, verificar a versão do cliente
psql
e ativar a extensãoamcheck
. Verifique se existem índices inconsistentes.
Uma declaração identifica violações de chaves únicas e principais, e outra declaração deteta uma variedade de outras inconsistências.
Corrija as inconsistências do índice.
A reindexação de um índice corrige todas as respetivas inconsistências. Pode ter de ajustar as definições de memória da instância para melhorar o desempenho.
Monitorize as operações de reindexação.
Recomendamos que monitorize o progresso da operação de reindexação para garantir que a operação está em curso e não está bloqueada.
Verifique se os índices são consistentes.
Depois de reindexar com êxito o índice, recomendamos que verifique se o índice não contém inconsistências.
Antes de começar
Faça uma cópia de segurança dos dados do cluster do AlloyDB
Para garantir que não são perdidos dados durante a reindexação, recomendamos que faça uma cópia de segurança dos dados do cluster. Para mais informações, consulte o artigo Crie uma cópia de segurança a pedido.
Defina a autorização alloydbsuperuser
Para concluir os passos nesta página, tem de ter alloydbsuperuser
autorizações. Para mais informações, consulte os papéis predefinidos do PostgreSQL do AlloyDB.
Certifique-se de que a versão do cliente psql
é 9.6 ou superior
Para concluir os passos nesta página, tem de garantir que a versão do cliente psql
é 9.6 ou superior. Execute o comando psql --version
para verificar a versão atual do cliente psql
.
Ative a extensão amcheck
Para verificar inconsistências do índice, tem de ativar a extensão amcheck
.
Exemplo de código
CREATE EXTENSION amcheck;
Verifique se existem índices inconsistentes
As secções seguintes descrevem como verificar a existência de índices inconsistentes, verificando as inconsistências de um índice, bem como as violações de chaves únicas e primárias.
Verifique se existem inconsistências
Em cada base de dados, execute a seguinte declaração para verificar se existem inconsistências:
Exemplo de código
DO $$ DECLARE r RECORD; version varchar(100); BEGIN RAISE NOTICE 'Started relhasindex validation on database: %', current_database(); FOR r IN SELECT indexrelid::regclass relname FROM pg_index WHERE indrelid NOT IN (SELECT oid FROM pg_class WHERE relhasindex) LOOP RAISE LOG 'Failed to check index %: %', r.relname, 'relhasindex is false, want true'; RAISE WARNING 'Failed to check index %: %', r.relname, 'relhasindex is false, want true'; END LOOP; RAISE NOTICE 'Finished relhasindex validation on database: %', current_database(); RAISE NOTICE 'Started b-tree amcheck on database: %', current_database(); SHOW server_version into version; SELECT split_part(version, '.', 1) into version; FOR r IN SELECT c.oid, c.oid::regclass relname, i.indisunique FROM pg_index i JOIN pg_opclass op ON i.indclass[0] = op.oid JOIN pg_am am ON op.opcmethod = am.oid JOIN pg_class c ON i.indexrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE am.amname = 'btree' AND c.relpersistence != 't' AND c.relkind = 'i' AND i.indisready AND i.indisvalid LOOP BEGIN RAISE NOTICE 'Checking index %:', r.relname; PERFORM bt_index_check(index => r.oid, heapallindexed => r.indisunique); EXCEPTION WHEN undefined_function THEN RAISE EXCEPTION 'Failed to find the amcheck extension'; WHEN OTHERS THEN RAISE LOG 'Failed to check index %: %', r.relname, sqlerrm; RAISE WARNING 'Failed to check index %: %', r.relname, sqlerrm; END; END LOOP; RAISE NOTICE 'Finished b-tree amcheck on database: %', current_database(); END $$;
Deve receber um resultado semelhante ao seguinte:
Exemplo de código
WARNING: Failed to check index t_i_key: relhasindex is false, want true NOTICE: Checking index t_pkey: NOTICE: Checking index t_i_key: WARNING: Failed to check index t_i_key: item order invariant violated for index "t_i_key" NOTICE: Checking index t_j_key: WARNING: Failed to check index t_j_key: item order invariant violated for index "t_j_key" NOTICE: Checking index ij: WARNING: Failed to check index ij: item order invariant violated for index "ij"
Para mais informações sobre como ver registos, consulte o artigo Veja registos através do Explorador de registos.
Identifique e corrija violações de chaves únicas e primárias
Esta secção descreve como verificar o índice quanto a violações de chaves únicas e primárias e, se existirem, como as corrigir.
Identifique violações de chaves únicas
As violações de chaves únicas têm de ser corrigidas antes de reindexar um índice. Para verificar todas as violações de chaves únicas, execute o seguinte comando em cada base de dados:
Exemplo de código
WITH q AS ( /* this gets info for all UNIQUE indexes */ SELECT indexrelid::regclass as idxname, indrelid::regclass as tblname, indcollation, pg_get_indexdef(indexrelid), format('(%s)',(select string_agg(quote_ident(attname), ', ') from pg_attribute a join unnest(indkey) ia(nr) on ia.nr = a.attnum where attrelid = indrelid)) as idxfields, COALESCE(substring(pg_get_indexdef(indexrelid) FROM '[)] (WHERE .*)$'), '') as whereclause FROM pg_index WHERE indisunique /* next line excludes indexes not affected by collation changes */ AND trim(replace(indcollation::text, '0', '')) != '' ) SELECT /* the format constructs the query to execute for each index */ format( $sql$ DO $$ BEGIN RAISE NOTICE 'checking index %3$I%2$I on table %1$I %4$I'; END;$$; SELECT this, prev, /* we detect both reversed ordering or just not unique */ (CASE WHEN this = prev THEN 'DUPLICATE' ELSE 'BACKWARDS' END) as violation_type FROM (SELECT %2$s AS this, lag(%2$s) OVER (ORDER BY %2$s) AS prev FROM %1$s %4$s ) s WHERE this <= prev and this IS NOT NULL and prev IS NOT NULL; /* change to just '<' if looking for reverse order in index */ $sql$, tblname, idxfields, idxname, whereclause ) FROM q -- LIMIT 20 /* may use limit for testing */ -- the next line tells psql to executes this query and then execute each returned line separately \gexec
A saída do script é semelhante à seguinte:
Resultado
NOTICE: checking index=users_email_key on table=users key_columns="(email)" NOTICE: checking index=games_title_key on table=games key_columns="(title)" this | prev | violation_type --------------------+--------------------+---------------- Game #16 $soccer 2 | Game #16 $soccer 2 | DUPLICATE Game #18 $soccer 2 | Game #18 $soccer 2 | DUPLICATE Game #2 $soccer 2 | Game #2 $soccer 2 | DUPLICATE Game #5 $soccer 2 | Game #5 $soccer 2 | DUPLICATE
Neste resultado, o cabeçalho da tabela NOTICE
mostra o índice, a coluna e a tabela
para os valores apresentados abaixo. Se o resultado contiver linhas que apresentem DUPLICATE
ou BACKWARDS
, significa que o índice está danificado e pode ter de ser corrigido. As linhas com BACKWARDS
indicam possíveis valores duplicados que podem estar ocultos. Se vir qualquer uma destas entradas na tabela, consulte o artigo Corrija violações de chaves duplicadas.
Corrija violações de chaves duplicadas
Se identificou um índice único duplicado ou se uma operação de reindexação falhar devido a um erro de violação de chave duplicada, conclua os passos seguintes para encontrar e remover as chaves duplicadas.
Extraia o
key_columns
do cabeçalho da tabelaNOTICE
, conforme mostrado no exemplo de saída anterior. No exemplo seguinte, a coluna de chave éemail
.Resultado
NOTICE: checking index=users_email_key on table=users key_columns="(email)"
Use estes valores em KEY_COLUMNS na consulta no passo 3.
Encontre o esquema da sua tabela. Use o
psql
para se ligar à sua base de dados e execute o seguinte comando:Exemplo de código
O valor na coluna\dt TABLE_NAME
schema
é o valor que usa para SCHEMA_NAME na consulta no passo 3.Por exemplo, para a seguinte consulta:
\dt games
O resultado é semelhante ao seguinte:
List of relations Schema | Name | Type | Owner --------+-------+-------+---------- public | games | table | postgres (1 row)
Execute as seguintes declarações para forçar uma análise completa da tabela e obter chaves duplicadas.
Exemplo de código
SET enable_indexscan = off; SET enable_bitmapscan = off; SET enable_indexonlyscan = off; SELECT KEY_COLUMNS, count(*) FROM SCHEMA_NAME.TABLE_NAME GROUP BY KEY_COLUMNS HAVING count(*) > 1;
Na declaração acima, KEY_COLUMNS são uma ou mais colunas abrangidas pelo índice único ou pela chave principal na tabela que está a verificar. Estas foram identificadas quando verificou se existiam violações de chaves únicas. A declaração devolve as chaves duplicadas e uma contagem dos duplicados para cada uma.
Por exemplo, para a seguinte consulta:
SELECT name,count(*) FROM public.TEST_NAMES GROUP BY name HAVING count(*) > 1;
O resultado é semelhante ao seguinte:
name | count --------------------+------- Johnny | 2 Peter | 2 (2 rows)
Neste caso, avance para o passo seguinte para remover as chaves duplicadas.
Se alguma das colunas em KEY_COLUMNS for nula, pode ignorá-la, uma vez que as restrições exclusivas não se aplicam a colunas NULL.
Se não forem encontradas chaves duplicadas, pode avançar para a secção Corrija índices inconsistentes.
Opcional, mas recomendado: crie uma cópia de segurança dos registos que contêm chaves duplicadas. Execute a seguinte declaração para criar registos de cópia de segurança:
Nesta declaração, KEY_VALUES é uma lista de valores copiados do resultado do passo anterior. Por exemplo:Exemplo de código
CREATE TABLE SCHEMA_NAME.TABLE_NAME_bak AS SELECT * FROM SCHEMA_NAME.TABLE_NAME WHERE (KEY_COLUMNS) IN (KEY_VALUES);
Exemplo de código
CREATE TABLE public.TEST_NAMES_bak AS SELECT * FROM public.TEST_NAMES WHERE (name) IN (('Johnny'),('Peter'))
Para um grande número de linhas, é mais fácil substituir o parâmetro ((KEY_VALUES)) na declaração
IN
pela declaraçãoSELECT
do passo 2 sem o parâmetrocount
. Por exemplo:Exemplo de código
CREATE TABLE SCHEMA_NAME.TABLE_NAME_bak AS SELECT * FROM SCHEMA_NAME.TABLE_NAME WHERE (KEY_COLUMNS) IN ( SELECT (KEY_COLUMNS) FROM SCHEMA_NAME.TABLE_NAME GROUP BY (KEY_COLUMNS) HAVING count(*) > 1);
Adicione uma função de replicação ao utilizador para desativar os acionadores:
Exemplo de código
ALTER USER CURRENT_USER with REPLICATION; SET session_replication_role = replica;
Execute a seguinte declaração para eliminar as chaves duplicadas:
Exemplo de código
BEGIN; DELETE FROM SCHEMA_NAME.TABLE_NAME a USING ( SELECT min(ctid) AS ctid, KEY_COLUMNS FROM SCHEMA_NAME.TABLE_NAME GROUP BY KEY_COLUMNS HAVING count(*) > 1 ) b WHERE a.KEY_COLUMNS = b.KEY_COLUMNS AND a.ctid <> b.ctid;
Por exemplo, para KEY_COLUMNS de várias colunas:
Exemplo de código
Onde day e rnum são KEY_COLUMNS.DELETE FROM public.test_random a USING ( SELECT min(ctid) AS ctid, day, rnum FROM public.test_random GROUP BY day, rnum HAVING count(*) > 1 ) b WHERE a.day=b.day and a.rnum = b.rnum AND a.ctid <> b.ctid;
A execução desta declaração mantém uma linha e elimina as outras para cada conjunto de linhas duplicadas. Se quiser controlar que versão da linha é eliminada, execute o seguinte filtro na declaração de eliminação:
Exemplo de código
DELETE FROM SCHEMA_NAME.TABLE_NAME WHERE ( KEY_COLUMNS, ctid) = (KEY_VALUES, CTID_VALUE);
Conclua os passos seguintes para verificar se o comando
DELETE
devolveu o número esperado de linhas sem erros:Execute a seguinte declaração para identificar as linhas nas quais as tabelas foram alteradas:
Exemplo de código
SELECT schemaname, relname, n_tup_del, n_tup_upd FROM pg_stat_xact_all_tables WHERE n_tup_del+n_tup_upd > 0;
Se todas as linhas estiverem corretas, confirme a transação
DELETE
:Exemplo de código
END;
Se existirem erros, reverta as alterações para os corrigir:
Exemplo de código
ROLLBACK;
Depois de eliminar as chaves duplicadas, pode reindexar o índice.
Corrija índices inconsistentes
As secções seguintes descrevem como pode corrigir as inconsistências de índice encontradas na sua instância.
Consoante a configuração da sua base de dados, pode ter de fazer o seguinte para cada índice identificado nos passos anteriores:
Se a operação de reindexação falhar devido a violações de chaves externas, tem de encontrar e corrigir estas violações.
Execute novamente a operação de reindexação.
Prepare-se para reindexar o seu índice
Encontre o tamanho do índice
A indexação de bases de dados maiores requer mais tempo do que a de bases de dados mais pequenas. Para melhorar a velocidade das operações de indexação e reindexação de bases de dados maiores, pode atribuir mais memória e potência do CPU a estas operações. Este é um passo importante no planeamento da operação de reindexação. Depois de saber o tamanho do índice, pode definir o tamanho da memória usado pela operação de reindexação e definir o número de trabalhadores paralelos.
Execute a seguinte declaração para encontrar o tamanho do índice, em kilobytes, do índice que quer corrigir:
Exemplo de código
SELECT i.relname AS index_name, pg_size_pretty(pg_relation_size(x.indexrelid)) AS index_size FROM pg_index x JOIN pg_class i ON i.oid = x.indexrelid WHERE i.relname = 'INDEX_NAME';
O resultado desta declaração é semelhante ao seguinte:
Resultado
index_name | index_size ------------+------------ my_index | 16 kB (1 row)
Defina o tamanho da memória a usar para a reindexação
Com base na dimensão do seu índice, conforme determinado na secção anterior, é importante definir o valor adequado para o parâmetro de configuração maintenance_work_mem
. Este parâmetro especifica a quantidade de memória a usar para a operação de reindexação. Por exemplo, se o tamanho do índice for superior a 15 GB, recomendamos que ajuste a memória de manutenção.
O exemplo seguinte mostra como definir maintenance_work_mem
:
Exemplo de código
SET maintenance_work_mem TO "1GB";
A indexação de bases de dados maiores requer mais tempo do que a de bases de dados mais pequenas. Para
melhorar a velocidade das operações de indexação e reindexação, recomendamos que defina
maintenance_work_mem
para, pelo menos, 2% da memória da instância para instâncias com
4 GB ou mais de memória durante esta operação de reindexação.
Defina o número de trabalhadores paralelos
Pode aumentar o número de trabalhadores paralelos para a reindexação definindo o parâmetro de configuração max_parallel_maintenance_workers
na base de dados. O valor predefinido deste parâmetro é 2, mas pode ser definido para um valor superior para aumentar o número de trabalhadores para a reindexação. Para instâncias com 8 ou mais núcleos de vCPU, recomendamos que defina o valor da flag max_parallel_maintenance_workers
como 4.
A imagem seguinte mostra como identificar os valores definidos para estes parâmetros:
Exemplo de código
SHOW max_parallel_maintenance_workers; SHOW max_worker_processes; SHOW max_parallel_workers;
O parâmetro max_parallel_maintenance_workers
é um subconjunto de
max_worker_processes
e está limitado por
max_parallel_workers
. Se precisar de mais trabalhadores paralelos, aumente o valor de max_worker_processes
e max_parallel_workers
.
O exemplo seguinte mostra como definir max_parallel_maintenance_workers
:
Exemplo de código
SET max_parallel_maintenance_workers TO 4;
O parâmetro max_parallel_maintenance_workers
não garante a atribuição de trabalhadores. Para confirmar que tem vários trabalhadores paralelos iniciados pela reindexação, execute a seguinte consulta a partir de outra sessão depois de iniciar a reindexação:
Exemplo de código
SELECT leader.leader_pid,leader.pid "worker_pid",leader.query FROM pg_stat_activity leader, pg_stat_activity worker WHERE leader.leader_pid = worker.pid;
Reindexe o seu índice
Pode reindexar um índice sem bloquear a carga de trabalho de produção através do utilitário pg_repack
. Esta utilidade automatiza e simplifica o processo de reindexação
concorrente, o que lhe permite reindexar sem tempo de inatividade. Para este procedimento, use a pg_repack
versão 1.4.7.
Conclua os passos seguintes para reindexar o seu índice através do pg_repack
:
Transfira, compile e instale a utilidade
pg_repack
a partir da página pg_repack.Crie a extensão
pg_repack
:Exemplo de código
CREATE EXTENSION pg_repack;
Execute o seguinte comando para reindexar o índice em simultâneo:
Exemplo de código
pg_repack -h HOSTIP -p 5432 -U USERNAME -d "DATABASE_NAME" -i "INDEX_NAME" --no-superuser-check --no-kill-backend --wait-timeout=3600
Este comando tem um resultado semelhante ao seguinte:
Resultado
INFO: repacking index "public.t_i_key"
Se ocorrerem erros ao executar
pg_repack
, pode corrigi-los e tentar novamente. Depois de corrigir todos os índices de chaves únicas e índices de chaves primárias, deve verificar se existem violações de chaves externas e corrigir as que forem encontradas.
Encontre e corrija violações de chaves externas
Para obter informações sobre como encontrar e corrigir violações de chaves externas, consulte o artigo Encontre e corrija violações de chaves externas.
Monitorize as operações de reindexação
Ocasionalmente, a operação de reindexação pode ser bloqueada por outras sessões. Recomendamos que verifique esta opção a cada 4 horas. Se a operação de reindexação estiver bloqueada, pode cancelar a sessão de bloqueio para que a operação de reindexação possa ser concluída.
Conclua os passos seguintes para identificar sessões de bloqueio e espera e, em seguida, cancelá-las na operação INDEX:
Para identificar sessões de bloqueio, execute a seguinte consulta:
Exemplo de código
SELECT pid, usename, pg_blocking_pids(pid) AS blocked_by, query AS blocked_query FROM pg_stat_activity WHERE cardinality(pg_blocking_pids(pid)) > 0;
Para cancelar uma sessão, execute a seguinte consulta com o PID da sessão de bloqueio da consulta anterior:
Exemplo de código
SELECT pg_cancel_backend(PID);
Verifique se os seus índices são consistentes
Tem de continuar a verificar inconsistências de índice para cada índice inconsistente. Depois de corrigir todas as violações de chaves e índices inconsistentes da sua instância, pode verificar se existem problemas seguindo os passos nas secções anteriores: