Replikacja bazy danych nie jest już ograniczona do konfiguracji Oracle-Oracle; Oracle-to-cloud i Oracle-to-BigQuery to tylko dwie z różnych opcji, które można teraz wybrać w konfiguracjach replikacji. W wielu z tych konfiguracji GoldenGate jest narzędziem z wyboru, biorąc pod uwagę jego wszechstronność i niezawodność. Niestety, podczas replikowania Oracle na inną platformę, działania takie jak modyfikacje tabeli mogą przeszkodzić w pracach. Dlatego pożądane byłoby śledzenie takich zmian w oczekiwaniu na obchodzenie się z ekstraktem GoldenGate z wdziękiem i szybko. Przyjrzyjmy się możliwym scenariuszom i określmy najlepszy kierunek działania.
Pierwszą myślą, jaką może mieć administrator DBA, jest ujednolicony audyt, ponieważ zapewnia on bogactwo informacji do działań podlegających audytowi. Niestety „tabela audytu” nie znajduje się na liście dostępnych uprawnień do audytu:
SCOTT @ orcl > create audit policy alter_tab_pol 2 privileges alter table; privileges alter table * ERROR at line 2: ORA-46355: missing or invalid privilege audit option. SCOTT @ orcl >
Co ciekawe, przywilej „ALTER ANY TABLE” jest podlega audytowi, ale nie kontroluje tego, co mogłoby zostać poddane audytowi:
SCOTT @ orcl > create audit policy table_pol 2 privileges create any table, alter any table, drop any table; Audit policy created. SCOTT @ orcl > audit policy table_pol; Audit succeeded. SCOTT @ orcl >
Taka polityka kontroluje tylko przyznawanie takich uprawnień innym użytkownikom i może nie zawsze generować rekord kontroli. Wymóg ten nie jest jeszcze spełniony przez audyty, więc należy opracować inne rozwiązanie. Na szczęście Oracle oferuje wyzwalacze na poziomie systemu, które mogą generować rekordy audytu dla takich działań. Przykład, jak można to zrobić, pokazano poniżej. Najpierw tworzona jest tabela zawierająca wygenerowane rekordy audytu:
create table ddl_log ( operation varchar2(30), obj_owner varchar2(35), object_name varchar2(35), sql_text varchar2(200), attempt_by varchar2(35), attempt_dt timestamp); create index ddl_log_idx on ddl_log(obj_owner, operation);
Tabela jest indeksowana na obj_owner i operacji, aby przyspieszyć generowanie raportów. Następnie tworzony jest wyzwalacz jako użytkownik będący właścicielem monitorowanych tabel, aby rejestrować wszystkie wykonane instrukcje CREATE, ALTER i DROP:
create or replace trigger ddl_trigger before create or alter or drop on schema declare oper ddl_log.operation%type; sql_text ora_name_list_t; i pls_integer; begin i := sql_txt(sql_text); if i = 1 then insert into ddl_log select ora_sysevent, ora_dict_obj_owner, ora_dict_obj_name, sql_text(1), user, v_systimestamp from dual; elsif i = 2 then insert into ddl_log select ora_sysevent, ora_dict_obj_owner, ora_dict_obj_name, sql_text(1)||sql_text(2), user, v_systimestamp from dual; elsif i >= 3 then insert into ddl_log select ora_sysevent, ora_dict_obj_owner, ora_dict_obj_name, sql_text(1)||sql_text(2)||sql_text(3), user, v_systimestamp from dual; end if; end ddl_trigger; /
Ponieważ liczba 64-bajtowych „kawałków” tekstu SQL może być dość duża, wyzwalacz ogranicza kolumnę SQL_TEXT do pierwszych trzech „kawałków”, dzięki czemu maksymalna długość ciągu wynosi 192 znaki. Zgodnie z oczekiwaniami w przypadku większych instrukcji, pełny tekst nie zostanie dostarczony, ale powinien zawierać w całości wszystkie instrukcje „zmień tabelę”. Należy zauważyć, że ten wyzwalacz przechwyci nie tylko instrukcje ALTER TABLE, ale także wszelkie instrukcje CREATE/ALTER/DROP przesłane do bazy danych. Oznacza to, że polecenia alter user, alter trigger, alter package, alter function, alter tablespace, alter system, create… i drop… również są rejestrowane w tabeli DDL_LOG. Z tego powodu stół może szybko rosnąć i stać się dość duży, dlatego należy stworzyć plan zachowania skończonej historii. W przypadku większości systemów 90 dni powinno wystarczyć na śledzenie zmian w tabeli w bazie danych. Raporty wygenerowane na podstawie zarejestrowanych danych mogą być przechowywane przez dłuższy czas (na przykład 12 miesięcy), zanim zostaną usunięte.
Przykładowy skrypt do zarządzania danymi tabeli znajduje się poniżej; wymusza 90-dniowe okno danych. Tworzony jest katalog dziennika:
mkdir -p /u01/app/oracle/ddl_chg/purge_logs
Skrypt SQL został napisany w celu usunięcia starych rekordów z DDL_LOG:
column sys_date new_value dt noprint column name new_value db_nm noprint select to_char(sysdate,'RRRRMMDD') sys_date from dual; select name from v$database; spool /u01/app/oracle/ddl_chg/purge_logs/ddl_log_purge_$db_nm._&dt..log set echo on -- -- Records slated for removal -- select * From ddl_log where attempt_dt < sysdate - 90; -- -- Delete selected records -- delete from ddl_log where attempt_dt < sysdate - 90; commit; spool off set echo off
To oczywiście nie może działać bezpośrednio z crona (lub podobnego harmonogramu), więc potrzebny jest skrypt opakowujący:
#!/bin/ksh # # purge_ddl_log_90.sh # # Shell script to purge old audit records # from the DDL_LOG table # # # Find the selected database and set the environment # set -A database `ps -ef | grep [p]mon | grep '<name>' | awk -F"_" '{print $3}'` for i in ${database[@]} # # Set the environment for the database # do ORACLE_SID=$i export ORACLE_SID ORAENV_ASK=NO export ORAENV_ASK unset ORACLE_BASE export ORACLE_BASE PATH=$PATH:<ORACLE_HOME/bin location> . <ORACLE_HOME/bin>/oraenv -s LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ORACLE_HOME/lib:$ORACLE_HOME/precomp/public export LD_LIBRARY_PATH PATH=$ORACLE_HOME/bin:$PATH export PATH # # Start SQL*Plus and execute the script # sqlplus /nolog <<EOF connect / as sysdba @/u01/app/oracle/ddl_chg/purge_ddl_log_90.sql EOF done # # Make the output files readable for all * cd /u01/app/oracle/ddl_chg/purge_logs chmod 666 *.log # # Remove old purge logs # find . -name "purge*log" -mtime +365 -exec /bin/rm -rf {} ;
Skrypt powłoki ustawia odpowiednie środowisko i ORACLE_SID na podstawie danych wyjściowych polecenia ps. Skrypt będzie musiał zostać wyedytowany, aby podać nazwę bazy danych do przeszukania i lokalizację ORACLE_HOME. Można podać więcej niż jedną nazwę bazy danych za pomocą | jako separator:
'abd|def|ghi|jkl'
Zapewnia to sposób na wyczyszczenie tabeli DDL_LOG w każdej bazie danych, w której zainstalowano tę kombinację tabeli/wyzwalacza. Nazwa bazy danych jest zawarta w nazwie pliku dziennika, aby oddzielić ślady czyszczenia dla każdej bazy danych. Czas przechowywania plików dziennika można zmienić, aby spełnić ograniczenia pamięci monitorowanego systemu.
Raporty zmian można generować na podstawie danych znalezionych w tabeli DDL_LOG:
set linesize 140 column sdate new_value sdt noprint select to_Char(sysdate, 'RRRRMMDDHH24')sdate from dual; column modlen new_value mlen noprint select 'a'||nvl(max(length(modification)),25) modlen From (select obj_owner owner, object_name tabname, substr(sql_text, instr(sql_text, 'modify ')) modification, attempt_dt mod_time from ddl_log where (instr(sql_text, 'alter table') > 0 or instr(sql_text, 'ALTER TABLE') > 0)); column objlen new_value olen noprint select 'a'||nvl(max(length(owner||'.'||tabname)),60) objlen From (select obj_owner owner, object_name tabname, substr(sql_text, instr(sql_text, 'modify ')) modification, attempt_dt mod_time from ddl_log where (instr(sql_text, 'alter table') > 0 or instr(sql_text, 'ALTER TABLE') > 0)); column modification format &mlen column mod_time format a29 column tab_name format &olen select owner||'.'|| tabname tab_name, modification, mod_time from (select obj_owner owner, object_name tabname, substr(sql_text, instr(sql_text, 'add ')) modification, attempt_dt mod_time from ddl_log where instr(lower(sql_text), 'alter table') > 0 union select obj_owner owner, object_name tabname, substr(sql_text, instr(sql_text, 'drop ')) modification, attempt_dt mod_time from ddl_log where instr(lower(sql_text), 'alter table') > 0 union select obj_owner owner, object_name tabname, substr(sql_text, instr(sql_text, 'modify ')) modification, attempt_dt mod_time from ddl_log where instr(lower(sql_text), 'alter table') > 0 union select obj_owner owner, object_name tabname, substr(sql_text, instr(sql_text, 'ADD ')) modification, attempt_dt mod_time from ddl_log where instr(lower(sql_text), 'alter table') > 0 union select obj_owner owner, object_name tabname, substr(sql_text, instr(sql_text, 'DROP ')) modification, attempt_dt mod_time from ddl_log where instr(lower(sql_text), 'alter table') > 0 union select obj_owner owner, object_name tabname, substr(sql_text, instr(sql_text, 'MODIFY ')) modification, attempt_dt mod_time from ddl_log where instr(lower(sql_text), 'alter table') > 0) dl where lower(dl.modification) not like '%table%' and mod_time >= trunc(systimestamp) order by 1, 3 spool /u01/app/oracle/ddl_chg/log/tab_chg_rpt_&sdt._&1..lst / spool off
Nazwa bazy danych jest przekazywana do skryptu, więc zostanie uwzględniona w nazwie pliku raportu. Kod zgłasza tylko zmiany w tabeli (stąd długi ciąg zapytań UNION) i generuje raport podobny do tego pokazanego poniżej:
TAB_NAME MODIFICATION MOD_TIME ---------------- ------------------------------ ----------------------------- SCOTT.DDL_LOG modify sql_text varchar2(200) 23-NOV-19 01.23.49.859971 PM
Skrypt ustawia również formatowanie kolumn na podstawie maksymalnej długości przechowywanych danych, aby ewentualnie zmniejszyć długość wiersza. Dane znacznika czasu zostały użyte w celu dostarczenia zarówno daty, jak i widocznej wartości czasu dla wygenerowanych rekordów zmian. Skrypty te zostały przetestowane, ale mogą wymagać pewnych modyfikacji w oparciu o implementację dostawcy systemu operacyjnego Linux/Unix.
W przypadku administratorów baz danych, którzy nie używają zreplikowanych systemów, może to nie być przydatne. Jednak w przypadku osób replikujących dane z Oracle do innych systemów (takich jak BigQuery, Snowflake itp.) wiedza o tym, kiedy nastąpiły zmiany w tabelach, może ułatwić radzenie sobie z błędami replikacji spowodowanymi przez te zmiany. Im szybciej proces replikacji może wrócić na właściwe tory, tym szybciej systemy, które opierają się na tych zreplikowanych danych, mogą powrócić do funkcjonalności.
# # #
Zobacz artykuły Davida Fitzjarrella