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

jexiocij  于 2023-06-22  发布在  PostgreSQL
关注(0)|答案(7)|浏览(147)

我希望有一种相当有效的方法来将整个表压缩为一个哈希值。
我有一些工具可以生成完整的数据表,然后可以使用这些数据表生成更多的表,等等。我试图实现一个简单的构建系统来协调构建运行并避免重复工作。我希望能够记录输入表的散列,以便稍后检查它们是否已更改。构建一个表需要几分钟或几个小时,所以花几秒钟构建哈希是可以接受的。
我使用的一个技巧是将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 = '');
hfyxw5xn

hfyxw5xn1#

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

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

wmtdaxz32#

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

dwbf0jvd3#

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

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
0lvr5msh

0lvr5msh4#

我有一个类似的需求,在测试一个专门的表复制解决方案时使用。
@Ben的滚动MD5解决方案(他附加到问题中)似乎很有效,但有几个陷阱让我绊倒了。
第一个问题(在其他一些答案中提到过)是,您需要确保在您正在检查的表上以已知的顺序执行聚合。其语法为eg。

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

f4t66c6m5#

Tomas Greif的solution很不错。但是对于足够大的表 * 无效内存分配请求大小 * 会出现错误。因此,它可以通过两种选择来克服。

**选项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
;

其中,group by子句中的3将行散列划分为16 777 216个批次(2:65 536,1:也可以使用其它批处理方法(例如:ntile)将起作用。
P.S.如果你需要比较两个表this post可能会有帮助。

eqfvzcg8

eqfvzcg86#

很棒的答案。
如果有人需要不使用聚合函数,而是维护对几个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
      first := FALSE;
      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'
);
quhf5bfb

quhf5bfb7#

至于算法,您可以对所有单个MD5散列进行XOR,或者将它们连接起来并对连接进行散列。
如果您想完全在服务器端完成此操作,则可能必须调用create your own aggregation function

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

作为中间步骤,您可以只选择所有行的MD5结果,并通过md5sum运行这些结果,而不是将整个表复制到客户机。
无论哪种方式,您都需要建立一个固定的排序顺序,否则,即使对于相同的数据,最终也可能得到不同的校验和。

相关问题