2010-10-26 14 views
14

Me gustaría una forma bastante eficiente de condensar una tabla completa en un valor hash.¿Cómo puedo obtener un hash de una tabla completa en postgresql?

Tengo algunas herramientas que generan tablas de datos completas, que luego pueden usarse para generar más tablas, y así sucesivamente. Estoy tratando de implementar un sistema de compilación simplista para coordinar ejecuciones de compilación y evitar repetir el trabajo. Quiero poder registrar hashes de las tablas de entrada para poder verificar luego si han cambiado. Construir una tabla lleva minutos u horas, por lo que es aceptable pasar varios segundos construyendo hash.

Un truco que he utilizado es simplemente canalizar la salida de pg_dump a md5sum, pero eso requiere la transferencia de todo el volcado de tabla a través de la red para hash it en el cuadro local. Idealmente, me gustaría producir el hash en el servidor de la base de datos.

Finding the hash value of a row in postgresql me da una forma de calcular un hash para una fila a la vez, que luego podría combinarse de alguna manera.

Cualquier consejo sería muy apreciado.

Editar para publicar lo que terminé con: La respuesta de tinychen no funcionó para mí directamente, porque no pude usar 'plpgsql' aparentemente. Cuando implementé la función en SQL, funcionó, pero fue muy ineficiente para tablas grandes. Entonces, en lugar de concatenar todos los hash de fila y luego hashing eso, cambié a usar un "hashing rodante", donde el hash anterior se concatena con la representación de texto de una fila y luego se crea un hash para producir el siguiente hash. Esto fue mucho mejor; aparentemente ejecutar md5 en cadenas cortas millones de veces adicionales es mejor que concatenar cadenas cortas millones de veces.

create function zz_concat(text, text) returns text as 
    'select md5($1 || $2);' language 'sql'; 

create aggregate zz_hashagg(text) (
    sfunc = zz_concat, 
    stype = text, 
    initcond = ''); 
+0

estoy al tanto de ninguna manera de hacer esto. Mi primer instinto sería registrar la creación de la tabla y comparar las marcas de tiempo. – mikerobi

+1

Supongo que no puedes simplemente ejecutar el comando pg_dump en el servidor, ¿verdad? –

+0

@Joey: +1. muy pragmático, probablemente el más rápido. Haz de esto una respuesta. – Thilo

Respuesta

7

simplemente haga esto para crear una función de agregación de tablas hash.

create function pg_concat(text, text) returns text as ' 
begin 
    if $1 isnull then 
     return $2; 
    else 
     return $1 || $2; 
    end if; 
end;' language 'plpgsql'; 

create function pg_concat_fin(text) returns text as ' 
begin 
    return $1; 
end;' language 'plpgsql'; 

create aggregate pg_concat (
    basetype = text, 
    sfunc = pg_concat, 
    stype = text, 
    finalfunc = pg_concat_fin); 

entonces podría utilizar la función pg_concat para calcular el valor hash de la tabla.

select md5(pg_concat(md5(CAST((f.*)AS text)))) from f order by id 
1

En cuanto al algoritmo, que podría XOR todos los hashes MD5 individuales, o concatenar y hash de la concatenación.

Si desea hacer esto completamente del lado del servidor, probablemente tenga que create your own aggregation function, que luego podría llamar.

select my_table_hash(md5(CAST((f.*)AS text)) from f order by id 

Como paso intermedio, en lugar de copiar toda la tabla para el cliente, usted podría seleccionar los resultados MD5 para todas las filas, y ejecutar aquellos a través de md5sum.

De cualquier forma debe establecer un orden de clasificación fijo, de lo contrario podría terminar con diferentes sumas de comprobación incluso para los mismos datos.

+0

"necesita establecer un orden de clasificación fijo". Eso es si quieres volver a generar los hashes. Para XOR esto no es necesario. Me hace pensar que XOR puede no ser una buena idea. – Thilo

+1

Tienes razón; XOR-agregando los hash significa que si tienes dos filas idénticas, y ambas cambian de la misma manera, el valor hash final será el mismo que el original. Las hileras idénticas probablemente no deberían estar allí, pero apostaría a que hay otras propiedades de XOR que aumentan las posibilidades de una colisión también. – Ben

+0

Gracias por el puntero; Voy a echar un vistazo a hacer esto. Desafortunadamente, uso muchos DB diferentes (y se crean nuevos todo el tiempo), así que tendré que guiar la creación de la función de agregación como parte del sistema de compilación. Volveré y acepto esta respuesta si no obtengo nada más. – Ben

16

Sé que esto es cuestión de edad, sin embargo, este es mi solución:

SELECT   
    md5(CAST((array_agg(f.* order by id))AS text)) /* id is a primary key of table (to avoid random sorting) */ 
FROM 
    foo f; 
1

que tenían un requisito similar, para usar cuando se prueba una solución de replicación mesa especializada.

@ La solución Rolling MD5 de Ben (que adjuntó a la pregunta) parece bastante eficiente, pero hubo un par de trampas que me hicieron tropezar.

La primera (mencionada en algunas de las otras respuestas) es que debe asegurarse de que el agregado se realiza en un orden conocido sobre la tabla que está comprobando. La sintaxis para eso es, por ejemplo.

select zz_hashagg(CAST((example.*)AS text) order by id) from example; 

Nota del order by está dentro del agregado.

La segunda es que usar CAST((example.*)AS text no dará resultados idénticos para dos tablas con el mismo contenido de columna a menos que las columnas se hayan creado en el mismo orden. En mi caso, que no estaba garantizada, por lo que para conseguir una verdadera comparación tuviera que lista las columnas por separado, por ejemplo:

select zz_hashagg(CAST((example.id, example.a, example.c)AS text) order by id) from example; 

Para completar (en el caso de una edición posterior debe eliminarlo) aquí es la definición de la zz_hashagg de @ pregunta de Ben:

create function zz_concat(text, text) returns text as 
    'select md5($1 || $2);' language 'sql'; 

create aggregate zz_hashagg(text) (
    sfunc = zz_concat, 
    stype = text, 
    initcond = ''); 
3
SELECT md5(array_agg(md5((t.*)::varchar))::varchar) 
    FROM (
     SELECT * 
      FROM my_table 
     ORDER BY 1 
     ) AS t 
0

grandes respuestas.

En caso de que por cualquier medio necesario que alguien no utilizar funciones de agregación, pero manteniendo el soporte para tablas de tamaño varios GiB, se puede utilizar este que tiene pequeñas penas de rendimiento sobre las mejores respuestas en el caso de las tablas más grandes.

CREATE OR REPLACE FUNCTION table_md5(
    table_name CHARACTER VARYING 
    , VARIADIC order_key_columns CHARACTER VARYING []) 
RETURNS CHARACTER VARYING AS $$ 
DECLARE 
    order_key_columns_list CHARACTER VARYING; 
    query CHARACTER VARYING; 
    first BOOLEAN; 
    i SMALLINT; 
    working_cursor REFCURSOR; 
    working_row_md5 CHARACTER VARYING; 
    partial_md5_so_far CHARACTER VARYING; 
BEGIN 
    order_key_columns_list := ''; 

    first := TRUE; 
    FOR i IN 1..array_length(order_key_columns, 1) LOOP 
    IF first THEN 
     first := FALSE; 
    ELSE 
     order_key_columns_list := order_key_columns_list || ', '; 
    END IF; 
    order_key_columns_list := order_key_columns_list || order_key_columns[i]; 
    END LOOP; 

    query := (
    'SELECT ' || 
     'md5(CAST(t.* AS TEXT)) ' || 
    'FROM (' || 
     'SELECT * FROM ' || table_name || ' ' || 
     'ORDER BY ' || order_key_columns_list || 
    ') t'); 

    OPEN working_cursor FOR EXECUTE (query); 
    -- RAISE NOTICE 'opened cursor for query: ''%''', query; 

    first := TRUE; 
    LOOP 
    FETCH working_cursor INTO working_row_md5; 
    EXIT WHEN NOT FOUND; 
    IF first THEN 
     SELECT working_row_md5 INTO partial_md5_so_far; 
    ELSE 
     SELECT md5(working_row_md5 || partial_md5_so_far) 
     INTO partial_md5_so_far; 
    END IF; 
    -- RAISE NOTICE 'partial md5 so far: %', partial_md5_so_far; 
    END LOOP; 

    -- RAISE NOTICE 'final md5: %', partial_md5_so_far; 
    RETURN partial_md5_so_far :: CHARACTER VARYING; 
END; 
$$ LANGUAGE plpgsql; 

utilizan como:

SELECT table_md5(
    'table_name', 'sorting_col_0', 'sorting_col_1', ..., 'sorting_col_n' 
); 
Cuestiones relacionadas