Las secuencias tienen espacios para permitir inserciones concurrentes. Intentar evitar huecos o volver a usar ID eliminados crea problemas de rendimiento horribles. Vea el PostgreSQL wiki FAQ.
PostgreSQL SEQUENCE
s se utilizan para asignar identificadores. Estos solo se incrementan y están exentos de las reglas habituales de reversión de transacciones para permitir que múltiples transacciones capturen nuevas ID al mismo tiempo. Esto significa que si una transacción se revierte, esas identificaciones se "descartarán"; no hay una lista de ID "gratis" guardadas, solo el contador de ID actual. Las secuencias también suelen incrementarse si la base de datos se cierra imprudentemente.
Las claves sintéticas (ID) son sin sentido de todos modos. Su orden no es significativo, su única propiedad de importancia es la singularidad. No se puede medir de manera significativa qué tan "distantes" están dos ID, ni se puede decir de manera significativa si uno es mayor o menor que otro. Todo lo que puedes hacer es decir "igual" o "no igual". Cualquier otra cosa es inseguro. No deberías preocuparte por las lagunas.
Si necesita una secuencia sin intervalo que reutilice los ID eliminados, puede tener uno, solo tiene que renunciar a una gran cantidad de rendimiento, en particular, no puede tener ninguna concurrencia en INSERT
en absoluto, porque tiene que escanear la tabla para obtener la ID libre más baja, bloqueando la tabla para escribir, de modo que ninguna otra transacción pueda reclamar la misma ID. Intenta buscar "secuencia sin salida postgresql".
El enfoque más simple es utilizar una tabla de contador y una función que obtenga la siguiente ID. Aquí hay una versión generalizada que usa una tabla contraria para generar identificaciones gapless consecutivas; sin embargo, no reutiliza los ID.
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 identificación, todas las demás transacciones que tratan de llamar get_next_id
bloquearán hasta que la primera transacción se confirma o se deshace. Esto es inevitable y para identificaciones gapless 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 contraria y agregue una cláusula WHERE
al UPDATE
que coincida con el parámetro al agregado columna. De esta forma puede tener múltiples filas de contador bloqueadas independientemente. Do no solo agregue columnas adicionales para los nuevos contadores.
Esta función no reutiliza los ID eliminados, solo evita la introducción de espacios vacíos.
Para volver a usar los IDs, les aconsejo ... que no vuelvan a usar los ID.
Si realmente debe, puede hacerlo añadiendo un disparador ON INSERT OR UPDATE OR DELETE
en la tabla de interés que añade elimina los ID a una mesa lateral lista libre, y los elimina de la tabla-lista libre cuando están INSERT
ed . Trate un UPDATE
como un DELETE
seguido de un INSERT
.Ahora modifique la función de generación de ID arriba para que haga un SELECT free_id INTO next_value FROM free_ids FOR UPDATE LIMIT 1
y si se encuentra, DELETE
s esa fila. IF NOT FOUND
obtiene una ID nueva de la tabla del generador de forma normal. Aquí es una extensión de la función no probada antes de apoyar 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;
MySQL no debe reutilizar autoincrement identificaciones, a menos que elimine las identificaciones más altos. – ceejayoz
¡Ah! gracias, eso es correcto OK - Puedo vivir con eso :) – fatfrog
De todos modos, no deberías preocuparte por los ID. Son solo números sin sentido. –