sql >> Base de Datos >  >> RDS >> PostgreSQL

Secuencias sin espacios de PostgreSQL

Las secuencias tienen espacios para permitir inserciones simultáneas. Intentar evitar las lagunas o reutilizar las identificaciones eliminadas crea horribles problemas de rendimiento. Consulte las preguntas frecuentes de la wiki de PostgreSQL.

PostgreSQL SEQUENCE Los s se utilizan para asignar ID. Estos solo aumentan y están exentos de las reglas habituales de reversión de transacciones para permitir múltiples transacciones para obtener nuevas identificaciones al mismo tiempo. Esto significa que si una transacción retrocede, esos ID se "desechan"; no se guarda una lista de identificaciones "gratuitas", solo el contador de identificaciones actual. Las secuencias también suelen incrementarse si la base de datos se cierra incorrectamente.

Las claves sintéticas (ID) son sin sentido de todas formas. Su orden no es significativo, su única propiedad significativa es la singularidad. No puede medir significativamente qué tan "separados" están dos ID, ni puede decir significativamente si uno es mayor o menor que otro. Todo lo que puedes hacer es decir "igual" o "no igual". Cualquier otra cosa es insegura. No deberías preocuparte por las lagunas.

Si necesita una secuencia sin pausas que reutilice las ID eliminadas, puede tener una, solo tiene que sacrificar una gran cantidad de rendimiento por ella; en particular, no puede tener ninguna concurrencia en INSERT s en absoluto, porque tiene que escanear la tabla en busca de la ID libre más baja, bloqueando la tabla para escritura para que ninguna otra transacción pueda reclamar la misma ID. Intente buscar "secuencia sin pausas de postgresql".

El enfoque más simple es usar una tabla de contadores y una función que obtenga la siguiente ID. Aquí hay una versión generalizada que usa una tabla de contadores para generar identificaciones consecutivas sin espacios; sin embargo, no reutiliza las identificaciones.

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

Tenga en cuenta que cuando una transacción abierta ha obtenido una ID, todas las demás transacciones que intentan llamar a get_next_id se bloqueará hasta que la primera transacción se confirme o retroceda. Esto es inevitable y para identificaciones sin espacios y es por diseño.

Si desea almacenar varios contadores para diferentes propósitos en una tabla, simplemente agregue un parámetro a la función anterior, agregue una columna a la tabla de contadores y agregue un WHERE cláusula a la UPDATE que hace coincidir el parámetro con la columna agregada. De esa manera, puede tener varias filas de contadores bloqueadas de forma independiente. no simplemente agregue columnas adicionales para nuevos contadores.

Esta función no reutiliza los ID eliminados, solo evita la introducción de espacios.

Para reutilizar identificaciones, aconsejo... no reutilizar identificaciones.

Si realmente debe hacerlo, puede hacerlo agregando un ON INSERT OR UPDATE OR DELETE activador en la tabla de interés que agrega los ID eliminados a una tabla auxiliar de lista libre y los elimina de la tabla de lista libre cuando están INSERT edición Tratar una UPDATE como DELETE seguido de INSERT . Ahora modifique la función de generación de ID anterior para que haga un SELECT free_id INTO next_value FROM free_ids FOR UPDATE LIMIT 1 y si lo encuentra, DELETE es esa fila. IF NOT FOUND obtiene una nueva ID de la tabla del generador como de costumbre. Aquí hay una extensión no probada de la función anterior para admitir la reutilización:

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;