【问题标题】:Fastest way to compute for hash of a whole table [duplicate]计算整个表的哈希的最快方法[重复]
【发布时间】:2016-02-22 19:35:20
【问题描述】:

我们需要能够为外部环境计算表哈希,并将其与来自内部环境的预先计算的哈希进行比较。使用它是为了确保外部环境中的数据不会被“流氓”数据库管理员篡改。 用户坚持使用此功能

目前,我们通过计算每个列值的单独散列来做到这一点,对列散列执行位异或得到行散列,然后对所有行散列执行位异或得到表散列。伪脚本如下:

cursor hash_cur is
select /*+ PARALLEL(4)*/ dbms_crypto.mac(column1_in_raw_type, HMAC_SH512, string_to_raw('COLUMN1_NAME')) as COLUMN1_NAME
       ...
from TABLE_NAME;

open hash_cur;
fetch hash_cur bulk collect into hashes;
close hash_cur;

for i in 1..hashes.count
loop
  rec := hashes(i);
  record_xor = rec.COLUMN1;
  record_xor = bit_xor(record_xor, rec.COLUMN2);
  ...
  record_xor = bit_xor(record_xor, rec.COLUMNN);

  table_xor = bit_xor(table_xor, record_xor);
end loop;

上面的伪脚本将使用 dbms_job 并行运行。

问题是我们有某些表的 TB 数据,而目前的性能不符合我们想要达到的性能。哈希必须“即时”完成,因为用户希望自己执行哈希检查。

  1. 你们有没有更好的方法来执行整个表哈希,或者基本上比较来自不同环境的表,这些表通过低延迟和相对低带宽的网络连接?

在我看来,该操作更多地受 CPU 限制而不是 I/O 限制。我正在考虑将表数据存储在一个 blob 中,其中数据按记录正确排列,然后按列排列。然后对输出文件执行哈希。这应该使操作完全受 I/O 限制。

  1. 最快的方法是什么?无论如何要在查询的 select 子句中执行此操作以消除任何开销 PL/SQL 到 SQL 引擎上下文切换?
    • 我正在考虑为此修改一个全局 blob
    • 还希望消除批量收集结果的 I/O 开销。

任何可以引导我获得更好执行脚本的建议将不胜感激。谢谢。

【问题讨论】:

  • 我不确定,但您可以将所有列数据附加到单个列中并创建一个哈希键,然后对其他表执行相同操作并比较这些哈希键。
  • @vishnusable 假定所有类型的数据都可以转换为 varchar 并附加。正如 #2 中所指出的,最好将所有数据聚合到二进制 lob。
  • 要计算表中所有行和列的哈希值,服务器必须读取所有这些数据。阅读整张表。首先肯定是 IO 绑定的。我会尝试找到一种散列算法,它可以在表中的值发生变化时更新散列。如果表中有数百万行并且一行发生更改,您不想再次读取所有数百万行来重新计算散列。不过,我不知道这种算法是否存在。
  • @VladimirBaranov 需要再次读取所有记录的原因是为了确保数据没有被具有提升访问权限的管理员篡改。这是我们的用户明确要求的。内部数据库中有一个触发器,它根据编辑的行更新表哈希,但对于外部,需要重新计算整个表哈希。如果不这样做,则永远无法知道外部数据已被篡改。
  • 您认为您的脚本会检测交换的两个不同行中的值吗?

标签: sql oracle plsql database-performance oracle12c


【解决方案1】:

首先,我认为接近“流氓管理员”的方法是结合 Oracle 的 审计跟踪Database Vault 功能。

也就是说,我可以尝试以下方法:

1) 创建一个自定义 ODCI 聚合函数来计算多行的哈希作为聚合。 2) 在表上创建一个 VIRTUAL NOT NULL 列,该列是表中所有列的 SHA 哈希 - 或者您关心保护的所有列。你会一直保持这种状态——基本上是以牺牲一些insert/update/delete 性能来换取能够更快地计算散列。 3) 在该虚拟列上创建一个非唯一索引 4) SELECT my_aggregate_hash_function(virtual_hash_column) FROM my_table 获取结果。

代码如下:

创建一个聚合函数来计算一组行的 SHA 哈希

CREATE OR REPLACE TYPE matt_hash_aggregate_impl AS OBJECT
(
  hash_value RAW(32000),
  CONSTRUCTOR FUNCTION matt_hash_aggregate_impl(SELF IN OUT NOCOPY matt_hash_aggregate_impl ) RETURN SELF AS RESULT,  
-- Called to initialize a new aggregation context
-- For analytic functions, the aggregation context of the *previous* window is passed in, so we only need to adjust as needed instead 
-- of creating the new aggregation context from scratch
  STATIC FUNCTION ODCIAggregateInitialize (sctx IN OUT matt_hash_aggregate_impl) RETURN NUMBER,
-- Called when a new data point is added to an aggregation context  
  MEMBER FUNCTION ODCIAggregateIterate (self IN OUT matt_hash_aggregate_impl, value IN raw ) RETURN NUMBER,
-- Called to return the computed aggragate from an aggregation context
  MEMBER FUNCTION ODCIAggregateTerminate (self IN matt_hash_aggregate_impl, returnValue OUT raw, flags IN NUMBER) RETURN NUMBER,
-- Called to merge to two aggregation contexts into one (e.g., merging results of parallel slaves) 
  MEMBER FUNCTION ODCIAggregateMerge (self IN OUT matt_hash_aggregate_impl, ctx2 IN matt_hash_aggregate_impl) RETURN NUMBER,
  -- ODCIAggregateDelete
  MEMBER FUNCTION ODCIAggregateDelete(self IN OUT matt_hash_aggregate_impl, value raw) RETURN NUMBER  
);

/

CREATE OR REPLACE TYPE BODY matt_hash_aggregate_impl IS

CONSTRUCTOR FUNCTION matt_hash_aggregate_impl(SELF IN OUT NOCOPY matt_hash_aggregate_impl ) RETURN SELF AS RESULT IS
BEGIN
  SELF.hash_value := null;
  RETURN;
END;


STATIC FUNCTION ODCIAggregateInitialize (sctx IN OUT matt_hash_aggregate_impl) RETURN NUMBER IS
BEGIN
  sctx := matt_hash_aggregate_impl ();
  RETURN ODCIConst.Success;
END;


MEMBER FUNCTION ODCIAggregateIterate (self IN OUT matt_hash_aggregate_impl, value IN raw ) RETURN NUMBER IS
BEGIN
  IF self.hash_value IS NULL THEN
    self.hash_value := dbms_crypto.hash(value, dbms_crypto.hash_sh1);
  ELSE 
      self.hash_value := dbms_crypto.hash(self.hash_value || value, dbms_crypto.hash_sh1);
  END IF;
  RETURN ODCIConst.Success;
END;

MEMBER FUNCTION ODCIAggregateTerminate (self IN matt_hash_aggregate_impl, returnValue OUT raw, flags IN NUMBER) RETURN NUMBER IS
BEGIN
  returnValue := dbms_crypto.hash(self.hash_value,dbms_crypto.hash_sh1);
  RETURN ODCIConst.Success;
END;

MEMBER FUNCTION ODCIAggregateMerge (self IN OUT matt_hash_aggregate_impl, ctx2 IN matt_hash_aggregate_impl) RETURN NUMBER IS
BEGIN
    self.hash_value := dbms_crypto.hash(self.hash_value || ctx2.hash_value, dbms_crypto.hash_sh1);
  RETURN ODCIConst.Success;
END;

-- ODCIAggregateDelete
MEMBER FUNCTION ODCIAggregateDelete(self IN OUT matt_hash_aggregate_impl, value raw) RETURN NUMBER IS
BEGIN
  raise_application_error(-20001, 'Invalid operation -- hash aggregate function does not support windowing!');
END;  

END;
/

CREATE OR REPLACE FUNCTION matt_hash_aggregate ( input raw) RETURN raw
PARALLEL_ENABLE AGGREGATE USING matt_hash_aggregate_impl;
/

创建一个要使用的测试表(因为你有你的真实表,所以你可以跳过这个)

create table mattmsi as select * from mtl_system_items where rownum <= 200000;

为每行数据创建一个虚拟列哈希。确保它是NOT NULL

alter table mattmsi add compliance_hash generated always as ( dbms_crypto.hash(to_clob(inventory_item_id || segment1 || last_update_date || created_by || description), 3 /*dbms_crypto.hash_sh1*/) ) VIRTUAL not null ;

在虚拟列上创建索引;这样,您可以通过对窄索引的完整扫描而不是对胖表的完整扫描来计算您的哈希

create index msi_compliance_hash_n1 on mattmsi (compliance_hash);  

把它们放在一起计算你的哈希

SELECT matt_hash_aggregate(compliance_hash) from (select compliance_hash from mattmsi order by compliance_hash);

几个cmets:

  1. 我认为使用散列来计算聚合很重要 (而不仅仅是在行级哈希上执行SUM(), 因为攻击者可以很容易地伪造正确的总和。
  2. 我认为您不能(容易?)使用并行查询,因为它是 重要的是,将行提供给 a 中的聚合函数 顺序一致,否则哈希值会改变。

【讨论】:

  • 您可能还想为 matt_hash_aggregate_impl 获取源哈希。
  • OP 在另一条评论中说,可疑的 DBA 将无法访问哈希检查算法,但也许不会受到伤害。同样,我认为 Oracle Database Vault 和审计可能是处理该问题的更可靠的方法。
  • 感谢 Matthew,以前从未遇到过虚拟列,它们看起来很有希望。但是,它不会像在触发器中那样具有相同的“安全问题”,其中“流氓管理员”可以禁用哈希计算触发器,以便数据的更改不会反映在哈希中?
  • 在阅读有关虚拟列的更多信息后,鉴于我的情况,我看不到任何优势,每次都会重新计算虚拟列值。此外,有人可以删除虚拟列。最好创建一个物理列并创建一个触发器来计算物理列的值。
  • 我假设当您计算哈希时,您可以验证虚拟列是否就位。流氓管理员可以篡改它,但不会以您无法检测到的方式进行篡改。触发器要糟糕得多:DBA 可以直接将哈希更新为他们需要隐藏更新的任何内容。
【解决方案2】:

您可以使用ORA_HASH 并作为表达式传递几列

select sum(ORA_HASH(col1||col2||col3)) as hash from my_table

但在这里,在 AskTom 上,有类似的讨论为什么它不是一个好方法:Creating a unique HASH value for the contents of a table

【讨论】:

  • 我认为 sum(hash) 不够强大。流氓 DBA 无法轻易计算单行来给出特定的哈希值,但他可以轻松地插入/删除具有真实哈希值的行以将 sum() 恢复为正确的值。
  • 有人能解释一下 sum 在这种情况下的作用吗?我以为 sum 只支持 base10 数字的算术?
  • @Learner 根据 ORA_HASH 自己的文档,它返回一个 NUMBER(因此这里是每行),所以将它们加在一起没有问题。但这也表明上面不会检测到一些变化,因为它对所有值求和。如果一个表有 2 行,其中 ORA_HASH 为 -2 和 +2,而另一个表的 ORA_HASH 值为 -1 和 +1,则 SELECT 将给出相同的结果,并且您会认为这些表是相同的,但实际上它们不是(当然是简单的例子,但显示了真正的问题)。
猜你喜欢
  • 2019-02-27
  • 2014-06-18
  • 2016-02-24
  • 1970-01-01
  • 2013-02-13
  • 1970-01-01
  • 1970-01-01
  • 2016-06-23
  • 2011-07-30
相关资源
最近更新 更多