5

假设您有一个相当大(对于“大”的本地定义)但相对稳定的表。

现在,我想对整个表的内容进行某种(任何类型)的校验和。

天真的方法可能是遍历整个表,获取每一行上每一列的连接的校验和(例如,MD5),然后可能连接它们并获取它的 MD5sum。

从客户端来看,可以通过将列的值逐步附加到 MD5 求和例程中来稍微优化,逐步改变值。

这样做的原因是,在将来的某个时候,我们想要重新连接到数据库,并确保没有其他用户可能已经改变了表:包括 INSERT、UPDATE 和 DELETE。

有没有更好的方法来确定特定表是否发生了任何更改?还是更有效/更快的方式?

更新/澄清:

  • 我们不能/不允许对表本身进行任何更改(例如添加“last-updated-at”列或触发器等)

(这适用于 Postgres,如果有帮助的话。我宁愿避免戳交易日志或类似的东西,但如果有办法这样做,我不反对这个想法。)

4

4 回答 4

7

添加列和触发器真的很安全

虽然我意识到您已经说过它是生产数据库中的一个大表,所以您说您不能修改它,但我想解释一下如何进行影响非常低的更改。

在 PostgreSQL 中,一个ALTER TABLE ... ADD COLUMN可为空的列只需要一些时间,并且不需要重写表。它确实需要一个独占锁,但其主要结果是它可能需要很长时间ALTER TABLE才能真正进行,它在等待获得锁的机会时不会阻止其他任何事情。

在表上创建触发器也是如此。

这意味着添加modified_atorcreated_at列和关联的触发器函数以将它们维护到在现实世界中密集使用的活动表中是非常安全的。在创建列之前添加的行将为空,这很有意义,因为您不知道何时添加/修改它们。每当行更改时,您的触发器将设置该modified_at字段,因此它们将逐渐被填充。

出于您的目的,拥有一个触发器维护的边表可能更有用,它跟踪表中任何位置的最后一次更改(插入/更新/删除)的时间戳。这将使您免于在磁盘上存储一大堆时间戳,并让您发现何时发生删除。使用触发器在每次更改时更新一行的单行边表FOR EACH STATEMENT成本非常低。由于争用,对于大多数表来说这不是一个好主意 - 它本质上会序列化所有尝试在行更新锁上写入表的事务。在您的情况下,这可能很好,因为表很大并且很少更新。

第三种选择是让侧表累积插入/更新/删除语句甚至单个行的时间戳的运行日志。这允许您的客户端读取更改日志表而不是主表,并对其缓存数据进行小幅更改,而不是使整个缓存失效并重新读取。缺点是您必须有办法定期清除旧的和不需要的更改日志记录。

所以......你不能改变桌子真的没有操作上的理由。尽管您知道这样做很安全,但很可能有商业政策原因阻止您这样做。

...但如果你真的,真的,真的不能:

另一种选择是使用现有的“md5agg”扩展:http ://llg.cubic.org/pg-mdagg/ 。或者,如果您是从源代码构建的,则应用当前流行的 pgsql-hackers 补丁将“md5_agg”添加到您的 PostgreSQL 安装的下一个版本。

逻辑复制

PostgreSQL 项目的双向复制产生的功能允许您侦听和重放逻辑更改(行插入/更新/删除),而无需对表进行触发器。pg_receivellog 工具在包含一些脚本时可能会很好地满足您的目的。

缺点是你必须运行一个修补过的 PostgreSQL 9.3,所以我猜如果你不能改变一个表,运行一堆将来可能会不兼容地改变的实验代码不会很高你的优先级列表;-)。它包含在 9.4 的库存版本中,请参阅“变更集提取”。

测试 relfilenode 时间戳不起作用

您可能认为您可以查看支持磁盘上表的文件的修改时间戳。这不会很有用:

  • 该表被拆分为多个扩展区,默认情况下每个文件为 1GB。因此,您必须在所有这些中找到最新的时间戳。
  • Autovacuum 活动将导致这些时间戳发生变化,可能在相应的写入发生后很长一段时间。
  • Autovacuum 必须定期对表内容进行自动“冻结”,以防止事务 ID 回绕。这涉及逐步重写表,并且自然会更改时间戳。即使在很长一段时间内没有添加任何内容,也会发生这种情况。
  • 提示位设置会导致SELECT. 这些写入也会影响文件时间戳。

检查事务日志

理论上,您可以尝试解码事务日志pg_xlogreader并找到影响感兴趣表的记录。您必须尝试排除由真空引起的活动、提示位设置后的整页写入,当然还有整个数据库集群中每个其他表的大量活动。

这对性能的影响可能是巨大的,因为必须检查对整个系统上每个数据库的每次更改。

总而言之,相比之下,在表上添加触发器是微不足道的。

于 2013-06-18T23:42:54.880 回答
1

在表上的插入/更新/删除事件上创建触发器怎么样?触发器可以调用将时间戳插入另一个表的函数,该函数将标记任何表更改事件的时间。

唯一需要考虑的是使用表中当前相同的数据更新的更新事件。即使表格没有真正改变,触发器也会触发。如果您担心这种情况,您可以让触发器调用一个函数,该函数仅针对更新的行生成校验和,并与先前生成的校验和进行比较,这通常比扫描和校验整个表更有效。

此处有关触发器的 Postgres 文档:http ://www.postgresql.org/docs/9.1/static/sql-createtrigger.html

于 2013-06-18T20:15:53.270 回答
1

如果您只是想知道表上次更改的时间而不对其进行任何操作,则可以查看数据库服务器上的实际文件时间戳。

SELECT relfilenode FROM pg_class WHERE relname = 'your_table_name';

如果您需要有关其确切位置的更多详细信息,可以使用:

select  t.relname,
        t.relfilenode,
        current_setting('data_directory')||'/'||pg_relation_filepath(t.oid)
from pg_class t
join pg_namespace ns on ns.oid = t.relnamespace
where relname = 'your_table_name';

既然你确实提到它是一个很大的表,它肯定会被分成段和祝酒词,但你可以利用 relfilenode 作为你的基点,并执行 ls -ltr relfilenode.* 或 relfilnode_* ,其中 relfilenode 是实际的relfilenode 从上面。

如果该表上发生了某些事情,这些文件会在每个检查点更新,因此根据检查点发生的频率,您会看到时间戳更新,如果您没有更改默认检查点间隔,则在几分钟内.

检查是否发生 INSERTS 或 DELETES 的另一种微不足道但不完美的方法是检查表大小:

SELECT pg_total_relation_size('your_table_name');

我不完全确定为什么触发器是不可能的,因为你不必让它具有追溯力。如果您的目标是确保其中没有任何更改,则可以将仅捕获插入、更新或删除事件的微不足道的触发器路由到另一个表,只是为尝试添加时间戳,但不会导致实际表上的任何活动。似乎您并不能确保任何事情都发生了变化,尽管只是知道某些事情发生了变化。

无论如何,希望这可以帮助您解决这个棘手的问题......

于 2013-06-18T21:25:47.130 回答
0

一种常见的做法是添加一modified列。如果是 MySQL,我会使用时间戳作为字段的数据类型(每次更新时更新到当前日期)。Postgre 必须有类似的东西。

于 2013-06-18T20:16:07.193 回答