Geschäftslogik in der Datenbank Best Practice Tobias Kreidel Datenbankentwickler Nis Nagel Datenbankentwickler Hamburg, 17.06.2010
Geschäftslogik in der Datenbank - Best Practice Standardfelder- und Trigger Exceptionhandling Regelmäßige Reorganisation von Tabellen Datenverteilung (Historisierung und Replikation) Kapselung von PL/SQL Sichtbarkeiten mit Virtual Private Database Rules Manager PL/PDF Import von Massendaten Anbindung externer Systeme Asynchrone Prozesse
Standardfelder- und Trigger Eindeutige technische ID. Wird über Before-Insert-Trigger ermittelt, wenn nicht übergeben TABELLE TABELLE_ID ... ERSTELLT_ID ERSTELLT_ZST GEAENDERT_ID GEAENDERT_ZST GEAENDERT_ZAHLER GELOESCHT_ZST Erstellt-Felder. Werden über Spalten-Defaults belegt Letzte Änderungs-Felder. Werden über einen Before-Update-Trigger ermittelt: :NEW.GEAENDERT_ZST := SYS_CONTEXT('VPD', 'USERID'); :NEW.GEAENDERT_ZST := SYSDATE; :NEW.GEAENDERT_ZAEHLER := :OLD.GEAENDERT_ZAEHLER + 1; Über den Gelöscht-Zst können einzelne Sätze logisch gelöscht werden
Exceptionhandling - Anforderungen Zentrale Definition von Exceptions (SQL-Codes) um die Mehrfachverwendung von Fehlercodes verhindern zu können Überblick über verwendete Exceptions
Exceptionhandling - Möglichkeiten DECLARE EINE_EXCEPTION EXCEPTION; PRAGMA EXCEPTION_INIT (EINE_EXCEPTION, -20003); BEGIN ... RAISE EINE_EXCEPTION; EXCEPTION WHEN EINE_EXCEPTION THEN END; Möglichkeit 2 CREATE OR REPLACE PACKAGE PA_EXCEPTIONS IS EINE_EXCEPTION EXCEPTION; K_EINE_EXCEPTION CONSTANT NUMBER(5) := -20003; PRAGMA EXCEPTION_INIT (EINE_EXCEPTION, -20003); END PA_EXCEPTIONS; ... BEGIN RAISE_APPLICATION_ERROR(K_EINE_EXCEPTION, ...); EXCEPTION WHEN EINE_EXCEPTION THEN END;
Exceptionhandling - Zentrale Definition Fehlertabelle FEHLER_CODE FEHLER_NUMMER FEHLER_TEXT EINE_EXCEPTION -20003 Fehler aufgetreten ... Package CREATE OR REPLACE PACKAGE PA_EXCEPTION IS PROCEDURE RAISE( IN_FEHLER_CODE IN VARCHAR2 , IN_FEHLER_TEXT IN VARCHAR2 DEFAULT NULL ); FUNCTION IST( IN_FEHLER_CODE IN VARCHAR2 , IN_FEHLER_NUMMER IN PLS_INTEGER DEFAULT SQLCODE ) RETURN BOOLEAN; FUNCTION GET_FEHLER_NUMMER( ) RETURN NUMBER; END PA_EXCEPTION;
Exceptionhandling - Anwendung BEGIN ... PA_EXCEPTION.RAISE('EINE_EXCEPTION'); EXCEPTION WHEN OTHERS THEN IF PA_EXCEPTION.IST('EINE_EXCEPTION') THEN END IF; END;
Exceptionhandling - Fazit zentrale (und dadurch eindeutige) Vergabe der Fehlercodes keine Gefahr von invaliden Objekte durch einen Fehler im zentralen Package keine Probleme bei der Entwicklung in größeren Teams
Regelmäßige Reorganisation von Tabellen - Anforderungen Operative Datenbank soll klein gehalten werden Zentrale und übersichtliche Definition der Reorg-Vorgänge Flexible und einfache Erweiterbarkeit und Anpassungsmöglichkeiten
Regelmäßige Reorganisation von Tabellen - Tabellenaufbau Gruppierung der Reorg-Vorgänge REORG_SET NAME Reihenfolge innerhalb des Reorg-Sets REORG REORG_SET REIHENFOLGE VARIANTE TABELLE LOESCH_BEDINGUNG AKTIV LETZTER_AUFRUF DELETE TRUNCATE SHRINK CALL Zu reorganisierende Tabelle / Bei CALL steht hier der Name der aufzurufenden Prozedur Soll Reorg-Vorgang durchgeführt werden? WHERE-Bedingung, die die zu löschenden Sätze beschreibt Zeitpunkt des letzten Reorg-Laufs
Regelmäßige Reorganisation von Tabellen - Ablauf Reorg-Sets durchlaufen Vorgänge des Reorg-Sets durchlaufen CASE VARIANTE WHEN 'DELETE' THEN EXECUTE IMMEDIATE 'DELETE FROM ' || TABELLE || ' WHERE ' || LOESCH_BEDINGUNG; WHEN 'TRUNCATE' THEN EXECUTE IMMEDIATE 'TRUNCATE TABLE ' || TABELLE; WHEN 'SHRINK' THEN EXECUTE IMMEDIATE 'ALTER TABLE '|| TABELLE ||' SHRINK SPACE'; WHEN 'CALL' THEN EXECUTE IMMEDIATE 'BEGIN ' || TABELLE || '; END;'; END CASE;
Regelmäßige Reorganisation von Tabellen - Fazit Läuft seit Einführung 2007 fehlerfrei Sehr flexibel bei kurzfristigen Änderungen
Datenverteilung (Historisierung und Replikation) Daten-Änderungen mitloggen Tabellen auf zwei Datenbanken synchron halten Sichern von Daten, die auf einer Instanz nur kurze Zeit vorgehalten werden sollen
Datenverteilung (Histor. und Repl.) – Bestandteile Quell - DB Ziel - DB LCR bauen (Capture-)Trigger Apply-Prozess Propagation-Prozess
Datenverteilung (Histor. und Repl.) – Methoden ALL / CHANGE Änderung hat neuen Eintrag in der Zieltabelle zur Folge Jede Änderung an einem Datensatz ist so nachvollziehbar ALL loggt auch die Anlage, CHANGE nur die Änderungen mit ARCHIVE / REPLICATION Änderungen in der Quelltabelle werden auch als Änderungen in die Zieltabelle übernommen ARCHIVE überträgt keine Löschungen, eine Reorganisation in der Quelltabelle hat dadurch keine Auswirkungen auf die Zieltabelle
Datenverteilung (Histor. und Repl.) – Methode LOG Quelltabelle (Capture-)Trigger Quelltabelle$LOG PK_ID LAST_CHANGED Geänderte Sätze für den Subscriber Aufräumen wenn alle Subscriber Änderung übernommen haben MODIFY_LOG QUELLTABELLE SUBSCRIBER LAST_IMPORT Zieltabelle z.B. MERGE Subscriber z.B. Export Auslesen und Hochsetzen des letzten Importdatums
Datenverteilung (Histor. und Repl.) – Generator HISTORICAL_TABLE TABELLE METHODE TRIGGER_DDL TABLE_DDL INIT_SKRIPT Tabelle in der über einen Trigger die nötigen Skripte generiert werden, um den Capture-Trigger, Historisierungstabellen und Initialisierungsskripte zu erzeugen
Datenverteilung (Histor. und Repl.) – Fazit Einfache, schnelle und komfortable Lösung zur Historisierung von Daten Hoher Erstellungsaufwand für den Generator Sehr flexible Verwendung der $LOG-Methode (andere Instanzen, Dateiexport, etc.)
Kapselung von PL/SQL - Problemstellung COBOL Zugriff von „alten“ COBOL-Programmen auf die DB erfolgt über eine Zugriffschicht, die nur Select, Insert, Update und Delete unterstützt Der Aufruf von PL/SQL-Sourcen soll ermöglicht werden Zugriffsschicht Nur Select, Insert, Update, Delete Datenbank
Kapselung von PL/SQL - Lösung Spalten Funktion Parameter Ergebnis Fehlercode UPDATE Funktion Parameter COBOL View SELECT PL/SQL Source 1 Instead-Of Trigger Package Ergebnis Fehlercode PL/SQL Source n
Kapselung von PL/SQL - Fazit Gute Möglichkeit um PL/SQL für „alte“ Systeme ansprechbar zu machen Package-Variablen sind aus SQL (der View) nicht ansprechbar => Wrapper-Funktionen nötig
Geschäftslogik in der Datenbank Sichtbarkeiten mit Virtual Private Database Einführung nach Börsencrash 1929 in den USA Trennung von Interessenskonflikten Research Kundenhandel Eigenhandel
Sichtbarkeiten mit VPD - Umsetzung -- LogOn-Trigger Anmelden -- VPDID: 1=Kundenhandel, 2=Eigenhandel, 3=... DBMS_SESSION.SET_CONTEXT ('VPD', 'VPDID', vpdid); CREATE OR REPLACE PACKAGE BODY OR.ORDER_SECURITY AS FUNCTION ORDER_SEC (schema IN VARCHAR2, tab IN VARCHAR2) RETURN VARCHAR2 IS BEGIN RETURN 'VPDID = '||SYS_CONTEXT('VPD','VPDID'); END; / DBMS_RLS.ADD_POLICY ( object_schema => 'OR', object_name => 'ORDER', policy_name => 'ORDER_POLICY', function_schema => 'OR', policy_function => 'ORDER_SECURITY.ORDER_SEC', statement_types => 'SELECT,INSERT,UPDATE,DELETE'); ORDER ORDER_ID ... VPD_ID
Sichtbarkeiten mit VPD - Fazit Sicher, kann nicht umgangen/vergessen werden Komplexe Zugriffsrechte möglich, aber… Materialized Views LogicalChangeRecords
Geschäftslogik in der Datenbank Rules Manager Es soll überwacht werden, dass Kundenorder an die Börse weitergeleitet und innerhalb einer bestimmten Zeit bestätigt werden. Dazu soll nach 1 Minute ein Popup nach 3 Minuten eine eMail erstellt werden.
Rules Manager - Umsetzung Popup, eMail Order übernehmen Kunde Datenbank Rules Manager Starten Kundenorder Stoppen
Rules Manager - Fazit komfortabel, um auf fehlende Ereignisse zu reagieren Reorganisieren der Events Sehr individuell anpassbar durch komplexe Eventstrukturen => überdimensioniert
Geschäftslogik in der Datenbank PL/PDF Tägliche Reports erstellen als pdf Reports direkt aus der Datenbank heraus drucken
PL/PDF - Umsetzung CREATE OR REPLACE PROCEDURE HELLOWORLD IS l_blob BLOB; BEGIN /* Initialize, without parameters means: page orientation: portrait; page format: A4 */ plpdf.init; plpdf.NewPage; /* Sets the font and its properties */ plpdf.SetPrintFont( p_family => 'Arial', -- Font family: Arial p_size => 12 -- Font size: 12 pt ); /* Draws a rectangle cell with text inside. The rectangle may have a border and fill color specified. */ plpdf.PrintCell( p_w => 50, -- Rectangle width p_h => 10, -- Rectangle heigth p_txt => 'Hello World!' -- Text in rectangle /* Returns the generated PDF document. The document is closed and then returned in the OUT parameter. */ plpdf.SendDoc(p_blob => l_blob); -- The generated document INSERT INTO STORE_BLOB (blob_file, created_date) VALUES (l_blob, sysdate); COMMIT; END;
PL/PDF - Fazit PL/PDF ist ausschließlich in PL/SQL geschrieben Erzeugung von PDF-Dokumenten direkt in der Datenbank Geringe Kosten kein WYSIWYG keine automatischen Spaltensummen einfache Charts möglich
Geschäftslogik in der Datenbank Import von Massendaten Es müssen täglich Dateien in unterschiedlichen Formaten importiert werden. Die Dateien beinhalten beispielsweise: Wertpapierkurse Stammdaten Datenabgleiche mit „End of Day“-Dateien
Import von Massendaten - Umsetzung Variante 1: Datei bereitstellen als „external table“ Dateistrukturen direkt in der Tabellendefinition angeben Laden der Daten per Merge-Statement Fehlerhafte Daten in Error-Tabelle schreiben Nachverarbeiten der Fehler Variante 2: Dateistruktur mit Hilfe eines PL/SQL-Type parsen Variante 3: Einzelverarbeitung in FOR LOOP anstatt MERGE Type zum Parsen und Speichern der Daten
Import von Massendaten – Beispiel MERGE INTO WP.KURSE s USING ( SELECT tab.typ.isin isin , tab.typ.boerse boerse_id , tab.typ.kurs_datum datum , tab.typ.kurs kurs , tab.typ.waehrung waehrung_id FROM (SELECT WP.TYPE_KURS(Ext.SATZ) Typ -- Type zum Parsen FROM WP.EXTERNAL_KURSE Ext ) Tab ) t ON ( s.ISIN = t.ISIN AND s.BOERSE_ID = t.BOERSE_ID AND s.WAEHRUNG_ID = t.WAEHRUNG_ID) WHEN MATCHED THEN UPDATE SET s.DATUM = t.DATUM , s.KURS = t.KURS WHERE s.DARUM < t.DATUM WHEN NOT MATCHED THEN INSERT (s.ISIN ,s.BOERSE_ID ,s.DATUM ,s.KURS ,s.WAEHRUNG_ID) VALUES (t.ISIN ,t.BOERSE_ID ,t.DATUM ,t.KURS ,t.WAEHRUNG_ID) LOG ERRORS INTO WP.KURSE_ERR (v_err_ident) -- Errorlogging REJECT LIMIT 100; -- Bei Fehlern eine Mail verschicken
Import von Massendaten - Fazit Möglichst Variante 1 benutzen Kein Abbruch bei einzelnen fehlerhaften Daten Optimizer hat Probleme mit „external tables“ Nachverarbeiten aus Error-Log schwierig Formatangabe in „external table“
Geschäftslogik in der Datenbank Anbindung externer Systeme mehrere interne Testinstanzen an eine externe Testinstanz anbinden einheitliche Kommunikation mit externen Systemen einfaches Handling von fachlichen Fehlern ermöglichen
Anbindung externer Systeme - Umsetzung Extern Produktion Berenberg Produktion Inbox Outbox Berenberg QS Berenberg Test Propagations Berenberg Integration Outbox Inbox Extern Test Berenberg Entw
Anbindung externer Systeme - Umsetzung Inbox Outbox DBMS_SCHEDULER Send Verarbeiten MessageTabelle Speichern TypeMessage View Verarbeiten nach Fehlern InsteadOfTrigger TypeXYZ Geschäftslogik
Anbindung externer Systeme - Fazit Sehr gute Erfahrungen Einfache und schnelle Nachverarbeitung im Fehlerfall Empfangen und Beantworten von Nachrichten in der gleichen Transaktion
Geschäftslogik in der Datenbank Asynchrone Prozesse Verschiedene Situationen erfordern es Aufrufe asynchron zu verarbeiten. z.B.: Performance „ORA-04091 mutating table“ Transaktionsicherer eMail Versand
Asynchrone Prozesse - Umsetzung Unterteilung in serielle und parallele Verarbeitung Standardattribute ID und IDENT zum Aufrufen der Funktionen Dispatcher-Package zum Verteilen der Aufrufe seriell Queues Dispatcher Package Aufruf DBMS_SCHEDULER parallel
Asynchrone Prozesse - Beispiel PROCEDURE APPLY_MESSAGE(IN_MSG IN SYS.XMLTYPE) IS v_ident VARCHAR2(50); v_id NUMBER(12); BEGIN v_ident := IN_MSG.EXTRACT('/async/ident//text()'); v_id := IN_MSG.EXTRACT('/async/parameter/id//text()'); CASE v_ident WHEN 'GLOBAL.EXPORT' THEN -- Export anstossen GLOBAL.PA_EXPORT.EXPORT_USER(IN_USER_ID => v_id); WHEN 'GLOBAL.VERARBEITE_ORDER' THEN -- Order einarbeiten GLOBAL.PA_ORDER.VERARBEITEN(IN_ORDER_ID => v_id); WHEN 'GLOBAL.MAIL' THEN -- eMail verschicken. GLOBAL.PA_MAIL.SEND_MAIL(IN_MAIL_ID => v_id); WHEN 'GLOBAL.MAIL_MESSAGE' THEN -- eMail verschicken. GLOBAL.PA_MAIL.SEND_MAIL(IN_MESSAGE => IN_MSG); WHEN 'ORDER.GATTUNG_LOESCHUNG' THEN -- Löschen einer Gattung ORDER.PA_GATTUNG.LOESCHE_GATTUNG(IN_GATTUNG_ID => v_id); ELSE RAISE_APPLICATION_ERROR(-20000,'QUEUE_DISPATCHER : Unbekannter Message Typ.'); END CASE; END;
Asynchrone Prozesse - Beispiel
Asynchrone Prozesse - Fazit „Bremsen“ im seriellen Ablauf verzögern nachfolgende Aufrufe Zustand der Datenbank ändert sich bis asynchrone Verarbeitung startet
Geschäftslogik in der Datenbank Best Practice Fragen? Anregungen? Geschäftslogik in der Datenbank Best Practice Tobias Kreidel Datenbankentwickler Nis Nagel Datenbankentwickler Hamburg, 17.06.2010