PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Sequências sem intervalos do PostgreSQL


As sequências têm lacunas para permitir inserções simultâneas. A tentativa de evitar lacunas ou reutilizar IDs excluídos cria problemas de desempenho horríveis. Consulte as perguntas frequentes do wiki do PostgreSQL.

PostgreSQL SEQUENCE s são usados ​​para alocar IDs. Eles só aumentam e estão isentos das regras usuais de reversão de transações para permitir que várias transações obtenham novos IDs ao mesmo tempo. Isso significa que, se uma transação for revertida, esses IDs serão "jogados fora"; não há nenhuma lista de IDs "livres" mantida, apenas o contador de IDs atual. As sequências também são geralmente incrementadas se o banco de dados for encerrado de forma imprópria.

As chaves sintéticas (IDs) são sem sentido qualquer maneira. Sua ordem não é significativa, sua única propriedade de significância é a singularidade. Você não pode medir significativamente a distância entre dois IDs, nem pode dizer significativamente se um é maior ou menor que o outro. Tudo o que você pode fazer é dizer "igual" ou "diferente". Qualquer outra coisa é insegura. Você não deve se preocupar com lacunas.

Se você precisar de uma sequência sem intervalos que reutilize IDs excluídos, você pode ter um, basta abrir mão de uma enorme quantidade de desempenho para isso - em particular, você não pode ter nenhuma simultaneidade em INSERT s, porque você precisa varrer a tabela para o ID livre mais baixo, bloqueando a tabela para gravação para que nenhuma outra transação possa reivindicar o mesmo ID. Tente pesquisar por "sequência sem intervalos do postgresql".

A abordagem mais simples é usar uma tabela de contador e uma função que obtém o próximo ID. Aqui está uma versão generalizada que usa uma tabela de contagem para gerar IDs consecutivos sem intervalos; ele não reutiliza IDs, no entanto.
CREATE TABLE thetable_id_counter ( last_id integer not null );
INSERT INTO thetable_id_counter VALUES (0);

CREATE OR REPLACE FUNCTION get_next_id(countertable regclass, countercolumn text) RETURNS integer AS $$
DECLARE
    next_value integer;
BEGIN
    EXECUTE format('UPDATE %s SET %I = %I + 1 RETURNING %I', countertable, countercolumn, countercolumn, countercolumn) INTO next_value;
    RETURN next_value;
END;
$$ LANGUAGE plpgsql;

COMMENT ON get_next_id(countername regclass) IS 'Increment and return value from integer column $2 in table $1';

Uso:
INSERT INTO dummy(id, blah) 
VALUES ( get_next_id('thetable_id_counter','last_id'), 42 );

Observe que quando uma transação aberta obteve um ID, todas as outras transações que tentarem chamar get_next_id bloqueará até que a 1ª transação seja confirmada ou revertida. Isso é inevitável e para IDs sem intervalos e ocorre por design.

Se você deseja armazenar vários contadores para diferentes propósitos em uma tabela, basta adicionar um parâmetro à função acima, adicionar uma coluna à tabela de contadores e adicionar um WHERE cláusula para o UPDATE que corresponde ao parâmetro para a coluna adicionada. Dessa forma, você pode ter várias linhas de contador bloqueadas independentemente. não basta adicionar colunas extras para novos contadores.

Esta função não reutiliza IDs excluídos, apenas evita a introdução de lacunas.

Para reutilizar IDs, aconselho... não reutilizar IDs.

Se você realmente precisa, pode fazê-lo adicionando um ON INSERT OR UPDATE OR DELETE acionado na tabela de interesse que adiciona IDs excluídos a uma tabela lateral de lista livre e os remove da tabela de lista livre quando eles são INSERT ed. Tratar um UPDATE como um DELETE seguido por um INSERT . Agora modifique a função de geração de ID acima para que ela faça um SELECT free_id INTO next_value FROM free_ids FOR UPDATE LIMIT 1 e se encontrado, DELETE é essa linha. IF NOT FOUND obtém um novo ID da tabela do gerador normalmente. Aqui está uma extensão não testada da função anterior para dar suporte à reutilização:
CREATE OR REPLACE FUNCTION get_next_id_reuse(countertable regclass, countercolumn text, freelisttable regclass, freelistcolumn text) RETURNS integer AS $$
DECLARE
    next_value integer;
BEGIN
    EXECUTE format('SELECT %I FROM %s FOR UPDATE LIMIT 1', freelistcolumn, freelisttable) INTO next_value;
    IF next_value IS NOT NULL THEN
        EXECUTE format('DELETE FROM %s WHERE %I = %L', freelisttable, freelistcolumn, next_value);
    ELSE
        EXECUTE format('UPDATE %s SET %I = %I + 1 RETURNING %I', countertable, countercolumn, countercolumn, countercolumn) INTO next_value;
    END IF;
    RETURN next_value;
END;
$$ LANGUAGE plpgsql;