2012-04-02 14 views
14

Me estoy moviendo de MySql a Postgres, y me di cuenta de que cuando elimina filas de MySql, los identificadores únicos para esas filas se vuelven a utilizar cuando crea nuevos. Con Postgres, si crea filas y las elimina, los identificadores únicos no se volverán a utilizar.PostgreSQL secuencias sin espacios

¿Hay alguna razón para este comportamiento en Postgres? ¿Puedo hacerlo actuar más como MySql en este caso?

+3

MySQL no debe reutilizar autoincrement identificaciones, a menos que elimine las identificaciones más altos. – ceejayoz

+0

¡Ah! gracias, eso es correcto OK - Puedo vivir con eso :) – fatfrog

+7

De todos modos, no deberías preocuparte por los ID. Son solo números sin sentido. –

Respuesta

40

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 SEQUENCEs 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; 
+0

"en particular, no puede haber ninguna concurrencia" --- realizar inserción, comprobar si se ha ejecutado correctamente. No hay motivo para bloquear toda la tabla (omg) – zerkms

+1

@zerkms ¿Sugiere que use algo como 'INSERT INTO some_table (id, ...) VALUES ((SELECT max (id) +1 FROM some_table), ...) 'y volver a probar los errores clave duplicados? Si es así, claro, puede hacerlo, pero no tendrá un mejor rendimiento que un enfoque que use bloqueo a nivel de tabla o fila para la generación de claves, generalmente peor debido al trabajo duplicado. Fundamentalmente, no puede funcionar mejor que un enfoque basado en bloqueo porque todavía puede tener una sola transacción escribiendo con éxito en cualquier momento dado. –

+0

no, quise decir llenar vacíos. De eso es de lo que estabas hablando, ¿verdad? La función del generador de ID debe ser no bloqueante, así como el procedimiento de inserción, pero con un manejo de violación de restricción único. – zerkms

Cuestiones relacionadas