如何在 postgresql 中获取整个表的哈希?
Posted
技术标签:
【中文标题】如何在 postgresql 中获取整个表的哈希?【英文标题】:How can I get a hash of an entire table in postgresql? 【发布时间】:2010-10-26 01:19:17 【问题描述】:我想要一种相当有效的方法来将整个表压缩为一个哈希值。
我有一些工具可以生成整个数据表,然后可以使用这些工具生成更多表,等等。我正在尝试实现一个简单的构建系统来协调构建运行并避免重复工作。我希望能够记录输入表的哈希值,以便稍后检查它们是否已更改。构建一张表需要几分钟或几小时,因此花费几秒钟来构建哈希是可以接受的。
我使用的一个技巧是将 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 = '');
【问题讨论】:
我不知道有什么方法可以做到这一点。我的第一反应是记录表的创建并比较时间戳。 我想你不能只在服务器上运行 pg_dump 命令吧? @Joey:+1。非常务实,可能是最快的。将此作为答案。 @Joey:好主意,但不,我无法在数据库服务器上运行命令 abstime (postgresql.org/docs/8.3/static/contrib-spi.html) 会是一个可能的解决方案吗?这样做的好处是可以原生地适应 Postgre... 【参考方案1】:我知道这是个老问题,但这是我的解决方案:
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;
【讨论】:
这是我见过的最简单的解决方案,对我来说效果很好。谢谢Tomas!【参考方案2】:SELECT md5(array_agg(md5((t.*)::varchar))::varchar)
FROM (
SELECT *
FROM my_table
ORDER BY 1
) AS t
【讨论】:
【参考方案3】:这样做就是为了创建一个哈希表聚合函数。
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
【讨论】:
不得不调整(postgres 11.7)到select md5(pg_concat(md5(CAST((f.*)AS text))order by id)) from f
。此外,对于我的大表@harmic's/@Ben 的解决方案在 1.5 分钟内运行,而此解决方案在 45 分钟后仍在运行【参考方案4】:
我有类似的要求,在测试专门的表复制解决方案时使用。
@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 = '');
【讨论】:
【参考方案5】:很好的答案。
如果有人要求不使用聚合函数但要保持对几 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'
);
【讨论】:
【参考方案6】:对于算法,您可以对所有单独的 MD5 散列进行异或运算,或者将它们连接起来并散列连接。
如果您想完全在服务器端执行此操作,您可能需要create your own aggregation function,然后您可以调用它。
select my_table_hash(md5(CAST((f.*)AS text)) from f order by id
作为中间步骤,您可以只选择所有行的 MD5 结果,然后通过 md5sum 运行,而不是将整个表复制到客户端。
无论哪种方式,您都需要建立固定的排序顺序,否则即使对于相同的数据,您最终也可能会得到不同的校验和。
【讨论】:
"你需要建立一个固定的排序顺序"。也就是说,如果您想重新散列哈希值。对于 XOR,这不是必需的。让我觉得 XOR 可能不是一个好主意。 你是对的;异或聚合哈希意味着如果您有两个相同的行,并且它们都以相同的方式更改,则最终的哈希值将与原始值相同。相同的行可能不应该存在,但我敢打赌 XOR 的其他属性也会增加碰撞的机会。 感谢指点;我会看看这样做。不幸的是,我使用了许多不同的数据库(并且一直在创建新的数据库),所以我必须编写聚合函数的创建脚本作为构建系统的一部分。如果我没有得到任何其他信息,我会回来接受这个答案。【参考方案7】:Tomas Greif 的solution 很好。但是对于足够大的表 invalid memory alloc request size 会发生错误。因此,可以通过 2 个选项来克服它。
选项 1. 不分批
如果表不够大,请使用string_agg
和bytea
数据类型。
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
:256)。其他批处理方法(例如严格的ntile
)也可以工作。
附:如果您需要比较两个表this post 可能会有所帮助。
【讨论】:
以上是关于如何在 postgresql 中获取整个表的哈希?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 PostgreSQL 中获取表的每一天的第一个日期并将其转换为 JSON
如何为复杂的 sql 查询获取中间数据。 PostgreSQL