sql - 计算整个表的哈希值的最快方法

标签 sql oracle plsql database-performance oracle12c

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

目前,我们通过计算每个列值的单独哈希值,对列哈希值执行位异或来获取行哈希值,然后对所有行哈希值执行位异或来得出表哈希值。伪脚本如下:

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 限制。

  • 最快的方法是什么?无论如何,要在查询的 select 子句中执行此操作,以消除 PL/SQL 到 SQL 引擎上下文切换的任何开销?
    • 我正在考虑为此修改一个全局 blob
    • 还希望消除批量收集结果的 I/O 开销。
  • 任何可以引导我制作出性能更好的脚本的建议将不胜感激。谢谢。

    最佳答案

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

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

    1) 创建自定义 ODCI 聚合函数来计算多行的哈希作为聚合。 2) 在表上创建一个 VIRTUAL NOT NULL 列,该列是表中所有列(或者您关心保护的所有列)的 SHA 哈希值。您可以一直保留它——基本上是牺牲一些插入/更新/删除性能来换取能够更快地计算哈希值。 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);
    

    一些评论:

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

    关于sql - 计算整个表的哈希值的最快方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33824571/

    相关文章:

    java - 将 Oracle 日期算术转换为在 HSQLDB 中工作

    c# - oracle连接到c#ssis脚本任务

    sql - Oracle SQL*Plus 接受语句

    oracle - 对象不再存在

    plsql - 将值从数组传递到 Apex 表单上的文本字段

    php - 在子查询中使用联接?

    MySQL innodb 文件

    mysql - SQL 中的简单数据透视表转置

    mysql - 如何使用 MYSQL 中的联接从多个表中获取多列并在非空列上显示数据以及在空列上显示 null 或零

    oracle - Oracle中的ADMIN选项和GRANT选项有什么区别