如何在postgresql中获取整个表的哈希值?

时间:2010-10-26 01:19:18

标签: sql postgresql hash

我想要一种相当有效的方法将整个表压缩为哈希值。

我有一些工具可以生成整个数据表,然后可以用它们来生成更多的表,依此类推。我正在尝试实现一个简单的构建系统来协调构建运行并避免重复工作。我希望能够记录输入表的哈希值,以便稍后检查它们是否已更改。建造一张桌子需要几分钟或几小时,所以花几秒钟建造哈希是可以接受的。

我使用过的hack只是将pg_dump的输出传递给md5sum,但是这需要通过网络传输整个表转储以在本地框上散列它。理想情况下,我想在数据库服务器上生成哈希值。

Finding the hash value of a row in postgresql为我提供了一种方法,可以一次计算一行的哈希值,然后以某种方式组合。

任何提示都将不胜感激。

编辑发布我最终的内容: tinychen的答案对我来说不起作用,因为我显然无法使用'plpgsql'。当我在SQL中实现该函数时,它起作用,但对于大型表来说效率非常低。因此,我没有连接所有行哈希然后哈希,而是切换到使用“滚动哈希”,其中前一个哈希与行的文本表示连接,然后进行哈希处理以产生下一个哈希。这要好得多;显然在短字符串上运行md5数百万次额外时间比连接短字符串数百万次更好。

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

7 个答案:

答案 0 :(得分:20)

我知道这是一个老问题,但这是我的解决方案:

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 :(得分:7)

就像这样创建一个哈希表聚合函数。

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

然后你可以使用pg_concat函数来计算表的哈希值。

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

答案 2 :(得分:6)

SELECT md5(array_agg(md5((t.*)::varchar))::varchar)
  FROM (
        SELECT *
          FROM my_table
         ORDER BY 1
       ) AS t

答案 3 :(得分:3)

我有一个类似的要求,在测试专门的表复制解决方案时使用。

@ Ben的滚动MD5解决方案(他附在问题上)看起来非常有效,但有几个陷阱让我感到沮丧。

第一个(在其他一些答案中提到)是您需要确保在您正在检查的表上以已知顺序执行聚合。其语法是例如。

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

请注意order by位于汇总内。

第二个问题是,除非列以相同的顺序创建,否则使用CAST((example.*)AS text将不会为具有相同列内容的两个表提供相同的结果。在我无法保证的情况下,为了得到真正的比较,我必须单独列出列,例如:

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

为了完整性(如果后续编辑应删除它),这里是来自@ Ben的问题的zz_hashagg的定义:

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

答案 4 :(得分:1)

对于算法,你可以对所有单独的MD5哈希进行异或,或者连接它们并对连接进行散列。

如果您想完全在服务器端执行此操作,则可能需要create your own aggregation function,然后您可以调用它。

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

作为中间步骤,您可以只选择所有行的MD5结果,而不是将整个表复制到客户端,而是通过md5sum运行。

无论哪种方式,您都需要建立一个固定的排序顺序,否则即使对于相同的数据,您也可能会得到不同的校验和。

答案 5 :(得分:1)

很棒的答案。

如果有人要求不使用聚合函数但是保持对大小为几个GiB的表的支持,那么在最大表的情况下,你可以使用性能惩罚而不是最佳答案

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;

用作:

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

答案 6 :(得分:1)

Tomas Greif 的 solution 不错。但是对于足够大的表,invalid memory alloc request size 会发生错误。因此,它可以通过 2 个选项来克服。

选项 1。无批次

如果表不够大,请使用 string_aggbytea 数据类型。

select
    md5(string_agg(c.row_hash, '' order by c.row_hash)) table_hash
from
    foo f
    cross join lateral(select ('\x' || md5(f::text))::bytea row_hash) c
;

选项 2。批量处理

如果前一个选项中的查询以类似错误结束

<块引用>

SQL 错误 [54000]:错误:内存不足 详细信息:无法将包含 1073741808 个字节的字符串缓冲区再扩大 16 个字节。

行数限制为1073741808 / 16 = 67108863,表应分批。

select
    md5(string_agg(t.batch_hash, '' order by t.batch_hash)) table_hash
from(
    select
        md5(string_agg(c.row_hash, '' order by c.row_hash)) batch_hash
    from
        foo f
        cross join lateral(select ('\x' || md5(f::text))::bytea row_hash) c
    group by substring(row_hash for 3)
    ) t
;

其中 3 子句中的 group by 将行哈希划分为 16 777 216 个批次(2:65 536,1:256)。其他批处理方法(例如严格的 ntile)也可以使用。

附言如果您需要比较两个表,this post 可能会有所帮助。