Oracle
 sql >> Database >  >> RDS >> Oracle

Il modo più veloce per calcolare l'hash di un'intera tabella

Prima di tutto, penso che il modo per avvicinarsi agli "amministratori canaglia" sia con una combinazione di audit trail di Oracle e Archivio database caratteristiche.

Detto questo, ecco cosa potrei provare:

1) Crea una funzione di aggregazione ODCI personalizzata per calcolare un hash di più righe come aggregato.2) Crea un VIRTUAL NOT NULL colonna sulla tabella che era un hash SHA di tutte le colonne della tabella o di tutte quelle che ti interessa proteggere. Lo terresti sempre in giro, in pratica scambiando alcuni insert/update/delete prestazioni in cambio per poter calcolare gli hash più rapidamente.3) Crea un indice non univoco su quella colonna virtuale4) SELECT my_aggregate_hash_function(virtual_hash_column) FROM my_table per ottenere i risultati.

Ecco il codice:

Crea una funzione aggregata per calcolare un hash SHA su un gruppo di righe

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

Crea una tabella di prova con cui lavorare (la salti poiché hai la tua tabella reale)

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

Crea un hash di colonna virtuale dei dati di ogni riga. Assicurati che sia 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 ;

Crea un indice sulla colonna virtuale; in questo modo puoi calcolare il tuo hash con una scansione completa dell'indice stretto invece di una scansione completa della fat table

create index msi_compliance_hash_n1 on mattmsi (compliance_hash);  

Metti tutto insieme per calcolare il tuo hash

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

Alcuni commenti:

  1. Penso che sia importante usare un hash per calcolare l'aggregato (piuttosto che fare semplicemente un SUM() sugli hash a livello di riga, perché un utente malintenzionato potrebbe falsificare la somma corretta molto facilmente.
  2. Non credo che tu possa (facilmente?) utilizzare query parallela perché è importante che le righe vengano inviate alla funzione di aggregazione in un ordine coerente, altrimenti il ​​valore hash cambierà.