DOAG Treffen September Erfahrungen mit der Partitionierungsoption des RDBMS Oracle Martin Bach
Überblick Strategie Weshalb Partitionierung? Partitionierung in OLTP/DWH Umgebungen Realisierung Indexe Performance Nachträgliche Partitionierung Probleme
Partitionierung: Strategie Eine große Tabelle wird in mehrere Teile zerlegt Zugriff muss transparent sein Zugriff auf die gesamte Tabelle oder selektiv auf einen Teil Der Optimizer muss dies alles an der Abfrage erkennen Keinerlei manueller Eingriff nötig
Weshalb partitionieren? Wartung Sliding Windows Data-Warehouse: Fact-Table Statistiken auf Partitionsebene Archivierung/Komprimierung einzelner Partitionen Performance Partition elemination Verteilung der Partitionen auf verschiedene Tablespaces Beschleunigung des Ladevorgangs bei DWH Umgebungen
Partitionierung für OLTP Performance-Vorteile Kleine Objekte = schnellerer Zugriff Zugriff dennoch transparent Zumeist Unterteilung nach Datum Vorteile für die Wartung Komprimierung alter Daten in einer Partition Index-Rebuild online benötigt weniger Zeit und (temporären) Speicher Tabellen Reorganisation auf Partitionsebene (parallel!) Höhere Verfügbarkeit
OLTP Umgebung - Fallstricke Ein falsch gewählter partition key kann alle Vorteile der Partitionierung zunichte machen! Alle Indexe müssen lokal sein, außer Primärschlüssel Am Besten ist der partition key Teil der Abfrage; Partitionen, die keine relevanten Informationen beinhalten werden von Verarbeitung ausgeschlossen ( partition elemination ) Aktivierung der Parallel-Option kann Performance negativ beeinflussen
Partitionierung im DWH Ursprünglicher Einsatzbereich Anforderung: Fakt-Tabellen müssen handhabbar sein Unterstützt Loading des ETL Prozesses hervorragend Lokale Indexe Statistiken auf Partitionsebene Partition Exchange Backup und Recovery einzelner Partitionen
Realisierung Arten von partitionierten Tabellen Range Partitioning Hash Partitioning List Partition (ab Oracle 9i) Composite range-hash Partitioning Composite range-list Partitioning (ab Oracle 9i) CREATE TABLE tabellenname (... ) PARTITION BY partitionsart ( schlüsselfelder ) ( partitionsdefinition ) enable row movement;
Range Partition Zeilen werden zu Partitionen mit Hilfe eines stetigen Bereichskriteriums (z.b. Zeit) zugeordnet Unter den folgenden Bedingungen hilfreich Daten lassen sich logisch unterteilen Daten sind näherungsweise gleichverteilt (sonst werden die Partitionen ungleichmäßig groß) Schlüsselfeld ist ein stetiges Merkmal, d.h. innerhalb seines Wertebereichs kann das Feld jede Ausprägung annehmen
Range-Partition: Beispiel Create table sales ( invoice_no number, sale_year int not null, sale_month int not null, sale_day int not null) partition by range (sale_year)( partition sales_02 values less than(2002), partition sales_03 values less than(2003), partition sales_04 values less than(2004)) enable row movement;
Hash Partition Zugehörigkeit zu Partition wird durch Hash- Funktion bestimmt Zeilen werden deterministisch zugewiesen, es soll eine Gleichverteilung der Zeilen auf die Partitionen erfolgen Anwendbar, wenn Keine probaten Schlüsselfelder für Range-Partitions erkennbar sind Dennoch Partitionierung aus Performance-/Management- Gründen gewünscht ist Das Schlüsselfeld möglichst unique ist (ideal:sequence)
Hash Partition - Beispiel CREATE TABLE vertraege ( ver_nummer number(9), constraint pk_ver primary key (ver_nummer),... ) partition by HASH (ver_nummer) partitions 4 store in (DAT01,DAT02,DAT03,DAT04) enable row movement;
List Partition Erlaubt genaue Kontrolle darüber, in welche Partition die Daten gelangen sollen (seit 9i) Nützlich, wenn Die Ausprägungen des Schlüsselfeldes diskret sind (z.b. Produkte, Länder,...) Voneinander unabhängige und unsortierte Daten als partition key dienen sollen Nur ein Schlüsselfeld verwendet wird Die Kardinalität des Schlüsselfeldes bekannt ist Achtung: ohne default Partition gibt es mit unbekannten Schlüsseln Probleme
List Partition-Beispiel CREATE TABLE sales_history (... ) PARTITION BY LIST (country) ( PARTITION europe VALUES ('United Kingdom', 'Germany', 'France'), PARTITION north_america VALUES ('United States', 'Canada', 'Mexico'), PARTITION south_america VALUES ('Brazil', 'Argentina'), PARTITION asia VALUES ('Japan', 'Korea'), PARTITION rest VALUES (DEFAULT)) enable row movement;
Composite Partitioning Bei SEHR großen Datenmengen Partition wird weiter unterteilt in Unterpartition Range/Hash Kombination Unterteilung nach stetigem Merkmal, Subpartitionen nutzen gleichmäßige Verteilungsfunktion des Hash- Algorithmus (kann auch auf versch. Tablespaces erfolgen) Range/List Kombination Ähnlich Range/Hash, aber genauere Kontrolle über die Partition, in die die Werte eingefügt werden sollen Wird schnell unhandlich (siehe Beispiel)
Range/Hash Partition: Beispiel CREATE TABLE SALES_HIST (...) PARTITION BY RANGE (sales_date) SUBPARTITION BY HASH (prod_id) subpartitions 10 store in (TS1, TS2, TS3, TS4, TS5) ( partition sales1 values less than (2001), partition sales2 values less than (2002), partition sales3 values less than (2003), partition sales4 values less than (2004) ) enable row movement;
Range/List Partitionierung: Beispiel create table sales_hist ( id number primary key, country char(2) not null, sales_date date, qty number(4) ) partition by range (sales_date) subpartition by list (country) ( partition fy_2000 values less than ('01.01.2001') ( subpartition fy_2000_europe values ('DE','AT'), subpartition fy_2000_asia values ('JP','KR') ), partition fy_2001 values less than ('01.01.2002') (...) ) enable row movement;
Indexe Global: über die ganze Tabelle Alle Partitionen werden bei Problemen mit dem Index unbrauchbar Einzige Möglichkeit Uniqueness zu realisieren Häufig in OLTP Umgebungen anzutreffen Lokal: Index für einzelne Partition Indexe unabhängig voneinander Hauptsächlich im DWH Umfeld anzutreffen Einfachere Wartung im Vergleich zu globalen Indexen
Lokale Indexe Local prefixed index Partitionsschlüssel ist Teil des Index und an erster Stelle Kann partition elemination durchführen Nützlich bei Joins Ist i.d.r. den non-prefixed Indexen vorzuziehen Local non-prefixed index: Partitionsschlüssel ist nicht Teil des Index Kann u.u. die Performance negativ beeinflussen Verwendung als sekundärer Index sinnvoll Local Index nicht als Primary Key verwendbar!
Globale Indexe In OLTP lt. Oracle den lokalen Indexen vorzuziehen Partitionierung kann unabhängig von der Struktur der zugrunde liegenden Tabelle sein global partitioned index global unpartitioned index Wenn partitioniert kann der Index Range- oder Hashpartitioned sein Bei range-partition nicht values less than (maxvalue) vergessen!
Performance: Übersicht Es ist der Cost Based Optimizer einzusetzen! DBMS_STATS zum Berechnen der Statistiken verwenden Eine bestimmte Partition abfragen: select * from sales partition (sales_q1) Der Optimizer kann Partitionen übergehen Beispiele
Performance I SQL> explain plan for select * from sh.sales where time_id < '31-DEC-1999' and prod_id = 13 Explained. MBACH@olp> select * from table(dbms_xplan.display); PLAN_TABLE_OUTPUT ------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------ Id Operation Name Rows Bytes Cost Pstart Pstop ------------------------------------------------------------------------------------------------------ 0 SELECT STATEMENT 139 4031 58 1 PARTITION RANGE ITERATOR 1 12 * 2 TABLE ACCESS BY LOCAL INDEX ROWID SALES 139 4031 58 1 12 3 BITMAP CONVERSION TO ROWIDS * 4 BITMAP INDEX SINGLE VALUE SALES_PROD_BIX 1 12 ------------------------------------------------------------------------------------------------------ PLAN_TABLE_OUTPUT ------------------------------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter("sales"."time_id"<to_date('1999-12-31 00:00:00', 'yyyy-mm-dd hh24:mi:ss')) 4 - access("sales"."prod_id"=13) 18 rows selected.
Performance II Nur die relevanten Partitionen wurden abgefragt select * from sh.sales where time_id < '31-DEC-99' and prod_id = 13 PART# HIGH VALUE PARTITION_NAME --------------------------------------- 1 1996-01-01 SALES_1995 2 1997-01-01 SALES_1996 3 1997-07-01 SALES_H1_1997 4 1998-01-01 SALES_H2_1997 5 1998-04-01 SALES_Q1_1998 6 1998-07-01 SALES_Q2_1998 7 1998-10-01 SALES_Q3_1998 8 1999-01-01 SALES_Q4_1998 9 1999-04-01 SALES_Q1_1999 10 1999-07-01 SALES_Q2_1999 11 1999-10-01 SALES_Q3_1999 12 2000-01-01 SALES_Q4_1999 13 2000-04-01 SALES_Q1_2000 14 2000-07-01 SALES_Q2_2000 15 2000-10-01 SALES_Q3_2000 16 2001-01-01 SALES_Q4_2000 17 2001-04-01 SALES_Q1_2001 18 2001-07-01 SALES_Q2_2001 19 2001-10-01 SALES_Q3_2001 20 2002-01-01 SALES_Q4_2001 21 2002-04-01 SALES_Q1_2002 22 2002-07-01 SALES_Q2_2002 23 2002-10-01 SALES_Q3_2002 24 2003-01-01 SALES_Q4_2002 25 2003-04-01 SALES_Q1_2003
Partition Exchange Beispiel In diesem Beispiel soll ein Sliding Window dargestellt werden Zuerst werden die alten Daten aus der Tabelle herausgelöst Dann werden die neuen Daten geladen und vorbereitet Zum Schluss wird die Fakt-Tabelle gesplittet und die neue Partition wird angehängt
Partition Exchange Basistabelle hat das Format: SQL> create table geschaeftsjahr ( budat date, id number ) 2 partition by range (budat) ( 3 partition gj_02 values less than ('01-JAN-2003'), 4 partition gj_03 values less than ('01-JAN-2004'), 5* partition rest values less than (maxvalue)) / SQL> create index gj_idx_loc on geschaeftsjahr(id) local nologging; Archivtabelle vorbereiten: SQL> create table gj_02_archiv tablespace STAGE as select * from geschaeftsjahr where 0=1; SQL> create index gj_02_archiv_idx on gj_02_archiv(id);
Partition Exchange II Partition austauschen gegen leere Tabelle (Dauer < 1 sek): SQL> alter table geschaeftsjahr exchange partition gj_02 2* with table gj_02_archiv including indexes without validation; Überprüfung: SQL> select count(1) from geschaeftsjahr partition (gj_02); COUNT(1) ---------- 0 SQL> select count(1) from gj_02_archiv; COUNT(1) ---------- 48138 Die Tabelle gj_02_archiv kann nun auf Band ausgelagert werden
Partition Exchange III Überprüfung (Forts.): SQL> select index_name,table_name,status from user_indexes 2* where table_name = 'GJ_02_ARCHIV'; INDEX_NAME TABLE_NAME STATUS ------------------ --------------- -------- GJ_02_ARCHIV_IDX GJ_02_ARCHIV VALID Load mit unabhängiger Tabelle vorbereiten und Exchange durchführen: SQL> create table gj_04 as select * from geschaeftsjahr where 1=0; -- nun die Daten laden SQL> create index gj_04_idx on gj_04 (id); SQL> alter table geschaeftsjahr split partition rest at ('01-JAN-2005') 2* into (partition gj_04, partition rest) SQL> alter table geschaeftsjahr exchange partition gj_04 2* with table gj_04 including indexes without validation;
Nachträgliche Partitionierung Nicht trivial, daher: vorher Sicherung erstellen! DBMS_REDEFINITION Siehe Beispiel Vereinzelt wird von Problemen berichtet Export/Import Evtl. mit Downtime verbunden! Funktionierte bereits in Testszenarien
DBMS_REDEFINITION Seit 9i; stark verbessert in 10g Wichtigste Merkmale Nachträgliche Unterstüzung für Partitionierung IOT zu Heap-Tabelle (und umgekehrt) Konversion Kann Online durchgeführt werden Daten von der alten Version der Tabelle werden in die neue Version der Tabelle überführt Danach werden die Einträge im Dictionary angepasst
Online Redefinition: Beispiel Kommt die Tabelle dafür in Frage? SQL>exec DBMS_REDEFINITION.CAN_REDEF_TABLE('MB', 'GESCHAEFTSJAHR',dbms_redefinition.cons_use_pk) PL/SQL procedure successfully completed Interimstabelle mit der neuen Struktur vorbereiten create table interim (...) partition by range (buchdat) ( partition p1 values less than ('01-JAN-2003'), partition p2 values less than (maxvalue)); Table created Den Prozess starten exec DBMS_REDEFINITION.start_redef_table(uname=>'MB', orig_table=>'geschaeftsjahr',int_table=>'interim')
Online Redefinition: Beispiel Kopie der Abhängigkeiten (ohne Indexe) declare err_cnt pls_integer := -1; begin dbms_redefinition.copy_table_dependents('mb', 'GESCHAEFTSJAHR', 'INTERIM', copy_indexes=>0, copy_triggers=>true, copy_constraints=>true, copy_privileges=>true, ignore_errors=>false, num_errors=>err_cnt); end; PL/SQL procedure successfully completed Wenn nötig, Interim-Tabelle synchronisieren Abschluss der Redefinition exec DBMS_REDEFINITION.finish_redef_table('MB', 'GESCHAEFTSJAHR','INTERIM');
Probleme mit Partitionierung Kein Row Movement für Tabelle aktiviert ORA-14402: updating partition key column would cause a partition change alter table tab enable row movement (ab 8.1.5) Kann auch auf Tablespace-Ebene definiert werden Ungünstige Indexe (non-prefixed) machen Abfragen langsamer als nötig Globaler Index unusable : skip_unusable_indexes
Ausblick Die hier vorgestellte Funktionalität ist nur ein Bruchteil dessen, was möglich ist! Feingranulare Zuweisung von Speicheroptionen zu jeder einzelnen Partition denkbar Range Partition: Schlüsselkriterium darf mehrspaltig sein (z.b. Jahr/Monat/Tag) Quellen Oracle 10g Administrator Guide Oracle 10g Data Warehousing Guide Oracle 10g SQL Reference
Star Schema: Beispiel