-- Anlage einer ersten Testtabelle -- mit 1M rows und einer Spalte (Breite: 50 Byte) -- Die Spalte wird mit 50 Sternen (*) gefüllt drop table test_col1_mpr; create table test_col1_mpr as select lpad('*', 50, '*') pad1 from dual connect by level <= 1000000; exec dbms_stats.gather_table_stats(user, 'TEST_COL1_MPR') -- Anlage einer ersten Testtabelle -- mit 100K rows und 10 Spalten (Breite: jeweils 50 Byte; insgesamt also 500 Bytes) -- Jede Spalte wird mit 50 Sternen (*) gefüllt drop table test_col10_mpr; create table test_col10_mpr as select lpad('*', 50, '*') pad1 , lpad('*', 50, '*') pad2 , lpad('*', 50, '*') pad3 , lpad('*', 50, '*') pad4 , lpad('*', 50, '*') pad5 , lpad('*', 50, '*') pad6 , lpad('*', 50, '*') pad7 , lpad('*', 50, '*') pad8 , lpad('*', 50, '*') pad9 , lpad('*', 50, '*') pad10 from dual connect by level <= 100000; exec dbms_stats.gather_table_stats(user, 'TEST_COL10_MPR')
Dazu ein paar Statistikwerte:
select table_name , pct_free , num_rows , blocks , avg_row_len from user_tables where table_name like 'TEST_COL%'; TABLE_NAME PCT_FREE NUM_ROWS BLOCKS AVG_ROW_LEN ------------------------------ ---------- ---------- ---------- ----------- TEST_COL10_MPR 10 100000 7265 510 TEST_COL1_MPR 10 1000000 7884 51
Dabei entspricht die AVG_ROW_LEN den Erwartungen, aber die Differenz bei den BLOCKS bedarf einer Erklärung:
with basedata as ( select blockid , count(*) row_count from (select dbms_rowid.rowid_block_number(rowid) blockid from TEST_COL1_MPR) group by blockid ) select row_count , count(*) cnt from basedata group by row_count order by row_count; ROW_COUNT CNT --------- ---------- 121 1 129 7751 with basedata as ( select blockid , count(*) row_count from (select dbms_rowid.rowid_block_number(rowid) blockid from TEST_COL10_MPR) group by blockid ) select row_count , count(*) cnt from basedata group by row_count order by row_count ROW_COUNT CNT --------- ---------- 12 1 14 7142
(Die Abweichung zwischen der über dbms_stats ermittelten BLOCKS-Angabe und der über dbms_rowid bestimmten Block-Anzahl schiebe ich dabei ohne weitere Prüfung auf dbms_stats)
Demnach packt Oracle also in der Regel 129 rows mit einer Breite von 51 Byte in einen Block der Tabelle TEST_COL1_MPR (129 * 51 = 6579), und 14 rows mit einer Breite von 510 Byte in die Blocks von TEST_COL10_MPR ( 14 * 510 = 7140). Angesichts des (default) PCT_FREE-Werts von 10, leuchtet mir die Füllung von TEST_COL10_MPR, aber TEST_COL1_MPR scheint deutlich weniger Sätze in jeden Block unterzubringen als möglich wäre - eine Beobachtung, die ich hier auch schon mal erwähnt und auf den Speicherplatzbedarf des row directories geschoben hatte. Bei Jonathan Lewis gibt's noch ein paar präzisere Erläuterungen zu diesem Aspekt. Demnach benötigt ein Satz insgesamt 5 Byte im row directory:
- size of each pointer in the row directory: 2 bytes
- minimum size of the “row”: 2 bytes (“flag” byte + “lock” byte)
- one extra byte for the column count – which will be zero for a completely null row
Wenn man diesen Wert in die Berechnung einbaut, nähern sich die Ergebnisse für die Testtabellen deutlich aneinender an:
- 129 * (51 + 5) = 7224
- 14 * (510 + 5) = 7210
Um die Untersuchung etwas stärker abzurunden, könnte man jetzt noch einen Test mit PCT_FREE = 0 und ohne ASSM durchführen, aber dazu fehlt mir gerade der Ehrgeiz - grundsätzlich kommt man jedenfalls recht genau auf die 8K, die im Block verfügbar sind (zu berücksichtigen wäre auch noch der Block-Header mit 57 Bytes). Stattdessen zurück zum Thema: wie gut lassen sich die beiden Test-Tabellen mit ihren überaus einheitlichen (und langweiligen) Inhalten komprimieren:
-- compression aktivieren alter table TEST_COL1_MPR compress; alter table TEST_COL10_MPR compress; -- Tabellen reorganisieren (erst dann wird die compression wirksam) alter table TEST_COL1_MPR move; alter table TEST_COL10_MPR move; -- Statistikerfassung exec dbms_stats.gather_table_stats(user, 'TEST_COL1_MPR') exec dbms_stats.gather_table_stats(user, 'TEST_COL10_MPR') select table_name , num_rows , blocks , avg_row_len from user_tables where table_name like 'TEST_COL%'; TABLE_NAME NUM_ROWS BLOCKS AVG_ROW_LEN ------------------------------ ---------- ---------- ----------- TEST_COL10_MPR 100000 151 510 TEST_COL1_MPR 1000000 1414 51
Nach den bisherigen Beobachtungen ist das jetzt ein einleuchtendes Ergebnis: die breitere Tabelle lässt sich deutlich stärker komprimieren, da sie die geringere Satzanzahl enthält und daher ein geringer Overhead für das (nicht komprimierbare) row directory entsteht. Pro Block lassen sich jetzt für beide Tabellen nahezu gleich viele komprimierte Sätze unterbringen:
-- TEST_COL1_MPR ROW_COUNT CNT --------- ---------- 171 1 709 1 724 1380 -- TEST_COL10_MPR ROW_COUNT CNT --------- ---------- 503 1 720 1 721 137
Als Ergebnis halte ich folgendes fest:
- wiederholte Werte lassen sich Attribut-übergreifend komprimieren (wie nicht anders zu erwarten war)
- breite Tabellen mit vielen Attributen und einer geringeren Satzanzahl lassen sich besser komprimieren als schmale Tabellen mit einer höheren Satzanzahl und wenigen Attributen, sofern es viele wiederholte Werte gibt. Verantwortlich dafür ist das row directory, dessen Platzbedarf von der Komprimierung nicht beeinflusst wird.
Keine Kommentare:
Kommentar veröffentlichen