Versuch 1: Range-List-Partitionierung mit zwei Spalten im Subpartition-Key
create table test_part_range_list ( storeid number , salesdate date , customerid number , customerflag number generated always as (case when customerid = 0 then 0 else 1 end )) partition by range (salesdate) subpartition by list (storeid, customerflag) subpartition template (subpartition sp1_cust values (1, 1) , subpartition sp2_cust values (2, 1) , subpartition sp1_nocust values (1, 0) , subpartition sp2_nocust values (2, 0) , subpartition sp_others values (default ) ) (partition p1 values less than ( to_date('01.01.2011','dd.mm.yyyy')), partition p2 values less than ( to_date('01.02.2011','dd.mm.yyyy')) ) ; subpartition template * FEHLER in Zeile 8: ORA-14304: List-Partitionierungsmethode erwartet eine einzelne Partitionierungsspalte
Und schon zeigt sich, dass es bisweilen ratsam ist, die Dokumentation etwas genauer anzuschauen: List-Partitionierung funktioniert offenbar nur mit einer einzelnen Schlüsselspalte, was diesen Versuch beendet...
Versuch 2: Range-List-Partitionierung mit einer virtuellen Spalte im Subpartition-Key
Im ersten Versuch hatte ich bereits eine virtuelle Spalte verwendet, um die Fallunterscheidung zwischen Transaktion mit Kundennummer und Transaktion ohne Kundennummer unterzubringen - ohne dafür eine "echte" Spalte definieren zu müssen, die die Breite der Sätze erhöhen würde. Virtual Column-Based Partitioning ist ein neues Feature in Version 11, scheint aber problemlos zu funktionieren.
create table test_part_range_list ( storeid number , salesdate date , customerid number , subpartcode number generated always as (case when customerid = 0 then storeid * 100 + 1 else storeid * 100 end )) partition by range (salesdate) subpartition by list (subpartcode) subpartition template (subpartition st1_nocust values (100) , subpartition st1_cust values (101) , subpartition st2_nocust values (200) , subpartition st2_cust values (201) , subpartition sp_others values (default ) ) (partition p1 values less than ( to_date('01.01.2011','dd.mm.yyyy')), partition p2 values less than ( to_date('01.02.2011','dd.mm.yyyy')) ); -- Testdaten insert into test_part_range_list (storeid, salesdate, customerid) values (1, '31.12.2010', 4711); insert into test_part_range_list (storeid, salesdate, customerid) values (1, '31.12.2010', 0); insert into test_part_range_list (storeid, salesdate, customerid) values (1, '31.01.2011', 4711); insert into test_part_range_list (storeid, salesdate, customerid) values (1, '31.01.2011', 0); insert into test_part_range_list (storeid, salesdate, customerid) values (2, '31.12.2010', 1234); insert into test_part_range_list (storeid, salesdate, customerid) values (2, '31.12.2010', 0); insert into test_part_range_list (storeid, salesdate, customerid) values (2, '31.01.2011', 1234); insert into test_part_range_list (storeid, salesdate, customerid) values (2, '31.01.2011', 0); -- Statistikerhebung auf Subpartitionsebene exec dbms_stats.gather_table_stats(user, 'TEST_PART_RANGE_LIST', Granularity => 'SUBPARTITION') -- Verteilung der Sätze auf die Subpartitionen select partition_name , subpartition_name , num_rows from dba_tab_subpartitions where table_name = 'TEST_PART_RANGE_LIST'; PARTITION_NAME SUBPARTITION_NAME NUM_ROWS ------------------------------ ------------------------------ ---------- P1 P1_ST1_NOCUST 1 P1 P1_ST1_CUST 1 P1 P1_ST2_NOCUST 1 P1 P1_ST2_CUST 1 P1 P1_SP_OTHERS 0 P2 P2_ST1_NOCUST 1 P2 P2_ST1_CUST 1 P2 P2_ST2_NOCUST 1 P2 P2_ST2_CUST 1 P2 P2_SP_OTHERS 0
Die Partitionierung führt also zunächst zur gewünschten Verteilung der Daten auf die Partitionen (wobei die Default-Sub-Partitionen leer bleiben; in ihnen landen alle Sätze, für die im Subpartition Template keine eigene (Sub-)Partition definiert ist. Nun aber zur Frage, ob diese Partitionierung sinnvolles Partition Pruning erlaubt - also den Ausschluss von Partitionen, die keine relevanten Daten enthalten, im Rahmen der Abarbeitung entsprechender Queries. Dazu liefert Autotrace folgende Ergebnisse:
select * from test_part_range_list STOREID SALESDAT CUSTOMERID SUBPARTCODE ---------- -------- ---------- ----------- 1 31.12.10 4711 100 1 31.12.10 0 101 2 31.12.10 1234 200 2 31.12.10 0 201 1 31.01.11 4711 100 1 31.01.11 0 101 2 31.01.11 1234 200 2 31.01.11 0 201 8 Zeilen ausgewõhlt. Abgelaufen: 00:00:00.04 Ausführungsplan ---------------------------------------------------------- Plan hash value: 1185884213 ------------------------------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop | ------------------------------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 8 | 136 | 21 (0)| 00:00:01 | | | | 1 | PARTITION RANGE ALL| | 8 | 136 | 21 (0)| 00:00:01 | 1 | 2 | | 2 | PARTITION LIST ALL| | 8 | 136 | 21 (0)| 00:00:01 | 1 | 5 | | 3 | TABLE ACCESS FULL| TEST_PART_RANGE_LIST | 8 | 136 | 21 (0)| 00:00:01 | 1 | 10 | ------------------------------------------------------------------------------------------------------------ Statistiken ---------------------------------------------------------- 0 recursive calls 0 db block gets 62 consistent gets 0 physical reads 0 redo size 917 bytes sent via SQL*Net to client 520 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 8 rows processed
Diese 62 consistent gets sind also der Referenzwert für das Lesen aller Partitionen. Die folgenden Autotrace-Listings sind aus Gründen der Übersichtlichkeit massiv gekürzt:
-- Datumseinschränkung select * from test_part_range_list where salesdate = '31.12.2010'; ------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Pstart| Pstop | ------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 4| | | | 1 | PARTITION RANGE SINGLE| | 4| 1 | 1 | | 2 | PARTITION LIST ALL | | 4| 1 | 5 | |* 3 | TABLE ACCESS FULL | TEST_PART_RANGE_LIST | 4| 1 | 5 | ------------------------------------------------------------------------------- Statistiken ---------------------------------------------------------- 33 consistent gets 4 rows processed --> partition pruning funktioniert (was auf dieser Ebene keine Überraschung ist) -- Einschränkung auf StoreId select * from test_part_range_list where storeid = 1; ---------------------------------------------------------------------------- | Id | Operation | Name | Rows | Pstart| Pstop | ---------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 4 | | | | 1 | PARTITION RANGE ALL| | 4 | 1 | 2 | | 2 | PARTITION LIST ALL| | 4 | 1 | 5 | |* 3 | TABLE ACCESS FULL| TEST_PART_RANGE_LIST | 4 | 1 | 10 | ---------------------------------------------------------------------------- Statistiken ---------------------------------------------------------- 62 consistent gets 4 rows processed --> kein Pruning: PARTITION RANGE ALL und PARTITION LIST ALL --> offenbar kann der Wert der virtuellen Spalte subpartcode nicht aus der StoreId abgeleitet werden -- Einschränkung auf CustomerId select * from test_part_range_list where customerid = 4711; ---------------------------------------------------------------------------- | Id | Operation | Name | Rows | Pstart| Pstop | ---------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 3 | | | | 1 | PARTITION RANGE ALL| | 3 | 1 | 2 | | 2 | PARTITION LIST ALL| | 3 | 1 | 5 | |* 3 | TABLE ACCESS FULL| TEST_PART_RANGE_LIST | 3 | 1 | 10 | ---------------------------------------------------------------------------- Statistiken ---------------------------------------------------------- 62 consistent gets 2 rows processed --> gleiches Verhalten wie bei der StoreId, was wiederum konsequent ist -- Kombinierte Bedingung mit StoreId und CustomerId select * from test_part_range_list where storeid = 1 and customerid = 4711; ---------------------------------------------------------------------------- | Id | Operation | Name | Rows | Pstart| Pstop | ---------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | | | 1 | PARTITION RANGE ALL| | 1 | 1 | 2 | | 2 | PARTITION LIST ALL| | 1 | 1 | 5 | |* 3 | TABLE ACCESS FULL| TEST_PART_RANGE_LIST | 1 | 1 | 10 | ---------------------------------------------------------------------------- Statistiken ---------------------------------------------------------- 62 consistent gets 2 rows processed --> auch die Kombination der beiden Basiswerte, aus denen sich die virtuelle Partitionsschlüsselspalte definiert, führt nicht zum Partition Pruning -- Bedinung mit der virtuellen Subpartitionsspalte select * from test_part_range_list where subpartcode = 201; ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Pstart| Pstop | ------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 2 | | | | 1 | PARTITION RANGE ALL | | 2 | 1 | 2 | | 2 | PARTITION LIST SINGLE| | 2 | KEY | KEY | | 3 | TABLE ACCESS FULL | TEST_PART_RANGE_LIST | 2 | KEY | KEY | ------------------------------------------------------------------------------- Statistiken ---------------------------------------------------------- 16 consistent gets 2 rows processed --> mit dem virtuellen subpartcode funktioniert das Pruning
Demnach sorgt die virtuelle Spalte "subpartcode" zwar für eine sinnvolle Verteilung der Daten auf die Subpartitionen, aber das Partition Pruning funktioniert auf Subpartitionsebene nur, wenn dieses Attribut auch als Bedingung in den zugehörigen Queries erscheint. Damit ist aber auch dieser zweite Fall nur noch von akademischem Interesse, da die Queries, die auf den Daten operieren sollen, nicht notwenig beide Einschränkungen enthalten - und schon gar nicht die virtuelle Zusatzspalte verwenden.
Da der Eintrag jetzt schon recht unübersichtlich geworden ist, verschiebe ich weitere Untersuchungen (Hash-Subpartitioning, Range-Partitioning mit mehreren Spalten) auf ein andermal.
Keine Kommentare:
Kommentar veröffentlichen