Jednym z projektów na studiach było stworzenie dużej bazy danych (około 1 000 000 rekordów), a następnie jej optymalizacja.
Moja pierwsza wersja bazy stworzona była według podanego powyżej schematu.
Wygenerowanie tylu sztucznych danych było możliwe dzięki stworzeniu kilku procedur i funkcji PL/SQL. Ostatecznie baza składała się z tylu elementów:
Tabela | Ilość wierszy |
meteorogramy | 1 022 811 |
komentarze | 11 593 |
meteorogramy_prognoz | 1 020 184 |
prognozy | 11 593 |
meteorogramy_miejscowosci | 1 020 184 |
miejscowosci | 22 |
mapy_szczegolowe | 602 836 |
mapy_szczegolowe_prognoz | 602 836 |
rodzaje_map_szczegolowych | 13 |
wspolrzedne_miejscowosci | 22 |
Suma |
4 292 094 |
Niestety, przy podstawowym zapytaniu wydobywającym podstawowe dane (meteorogram i komentarz do niego):
SELECT meteorogramy.meteorogram, komentarze.tekst_komentarza FROM meteorogramy, komentarze WHERE GodzinaDaty(meteorogramy.godzina) = 0 AND meteorogramy.id_meteorogramu IN (SELECT id_meteorogramu FROM meteorogramy_prognoz JOIN prognozy USING(id_prognozy) WHERE prognozy.data = to_date('1994-01-01 00:00', 'YYYY-MM-DD HH24:MI') ) AND meteorogramy.id_meteorogramu IN (SELECT id_meteorogramu FROM meteorogramy_miejscowosci JOIN miejscowosci USING(id_miejscowosci) WHERE miejscowosci.nazwa_miejscowosci = 'Gdynia' ) AND komentarze.id_komentarza = (SELECT id_komentarza FROM prognozy WHERE data = to_date('1994-01-01 00:00', 'YYYY-MM-DD HH24:MI') );
plan tego zapytania nie był optymalny:
OPERATION | OPTIONS | CPU_COST | IO_COST | BYTES | OBJECT_NAME |
SELECT STATEMENT |
28177345 |
290 |
60984 |
||
MERGE JOIN | CARTESIAN |
25062649 |
105 |
2772 |
|
NESTED LOOPS | |||||
NESTED LOOPS |
25025884 |
101 |
448 |
||
VIEW |
23320173 |
12 |
1144 |
VW_NSO_1 | |
HASH | UNIQUE |
23320173 |
12 |
2024 |
|
NESTED LOOPS | |||||
NESTED LOOPS |
1792635 |
12 |
2024 |
||
TABLE ACCESS | FULL |
1737860 |
9 |
15 |
PROGNOZY |
INDEX | RANGE SCAN |
32693 |
2 |
FK_METEO_PROGN_ID_PROGNOZY | |
TABLE ACCESS | BY INDEX ROWID |
54774 |
3 |
704 |
METEOROGRAMY_PROGNOZ |
INDEX | UNIQUE SCAN |
9021 |
1 |
SYS_C0072042 | |
NESTED LOOPS |
31296 |
4 |
18 |
||
TABLE ACCESS | BY INDEX ROWID |
8341 |
1 |
11 |
MIEJSCOWOSCI |
INDEX | UNIQUE SCAN |
1050 |
0 |
NAZWA_MIEJSCOWOSCI_UNIQUE | |
TABLE ACCESS | BY INDEX ROWID |
22954 |
3 |
7 |
METEOROGRAMY_MIEJSCOWOSCI |
INDEX | RANGE SCAN |
15493 |
2 |
FK_METEO_MIEJS_ID_METEOROGRAMU | |
TABLE ACCESS | BY INDEX ROWID |
19383 |
2 |
99 |
METEOROGRAMY |
BUFFER | SORT |
25043266 |
103 |
581 |
|
TABLE ACCESS | BY INDEX ROWID |
9191 |
1 |
581 |
KOMENTARZE |
INDEX | UNIQUE SCAN |
1900 |
0 |
SYS_C0072045 | |
TABLE ACCESS | FULL |
1737880 |
9 |
12 |
PROGNOZY |
Optymalizacja polegała na wyeliminowaniu złączeń (JOIN) i nałożeniu indeksów, co wiązało się z przeprojektowaniem bazy:
Wówczas nowe zapytanie wyglądało tak:
SELECT meteorogramy.meteorogram, prognozy.tekst_komentarza FROM meteorogramy, prognozy WHERE meteorogramy.igodzina = 0 AND meteorogramy.id_meteorogramu IN (SELECT id_meteorogramu FROM meteorogramy_prognoz WHERE meteorogramy_prognoz.data = to_date('1994-01-01 00:00', 'YYYY-MM-DD HH24:MI') ) AND meteorogramy.id_meteorogramu IN (SELECT id_meteorogramu FROM meteorogramy_miejscowosci JOIN miejscowosci USING(id_miejscowosci) WHERE miejscowosci.nazwa_miejscowosci = 'Gdynia' ) AND prognozy.data = to_date('1994-01-01 00:00', 'YYYY-MM-DD HH24:MI');
A plan zapytania tak:
OPERATION | OPTIONS | CPU_COST | IO_COST | BYTES | OBJECT_NAME |
SELECT STATEMENT |
71413 |
9 |
700 |
||
NESTED LOOPS | SEMI |
71413 |
9 |
700 |
|
NESTED LOOPS |
40487 |
5 |
696 |
||
MERGE JOIN | CARTESIAN |
24084 |
3 |
594 |
|
TABLE ACCESS | BY INDEX ROWID |
15913 |
2 |
587 |
PROGNOZY |
INDEX | RANGE SCAN |
8371 |
1 |
DATA | |
BUFFER | SORT |
8171 |
1 |
7 |
|
SORT | UNIQUE |
8171 |
1 |
7 |
|
TABLE ACCESS | BY INDEX ROWID |
8171 |
1 |
7 |
METEOROGRAMY_PROGNOZ |
INDEX | RANGE SCAN |
8171 |
1 |
DATA2 | |
TABLE ACCESS | BY INDEX ROWID |
16403 |
2 |
102 |
METEOROGRAMY |
INDEX | UNIQUE SCAN |
9021 |
1 |
SYS_C0072042 | |
VIEW PUSHED PREDICATE |
30926 |
4 |
4 |
VW_NSO_1 | |
NESTED LOOPS |
30926 |
4 |
19 |
||
TABLE ACCESS | BY INDEX ROWID |
8341 |
1 |
11 |
MIEJSCOWOSCI |
INDEX | UNIQUE SCAN |
1050 |
0 |
NAZWA_MIEJSCOWOSCI_UNIQUE | |
TABLE ACCESS | BY INDEX ROWID |
22584 |
3 |
8 |
METEOROGRAMY_MIEJSCOWOSCI |
INDEX | RANGE SCAN |
15293 |
2 |
FK_METEO_MIEJS_ID_METEOROGRAMU |
Wnioski
Wzrost wydajności jest wielokrotny (CPU_COST – ok. 395 razy mniejszy, IO_COST – ok. 32 razy mniejszy w porównaniu do 1-go planu zapytania). Spowodowane to jest tym, że Oracle już nie musi wiele razy sięgać do tabeli prognozy, opiera się jedynie na indeksach z jednej tabeli, dzięki czemu optymalizator nie musi wykonywać pętli zagnieżdżonych wielokrotnie, co wielokrotnie odzwierciedla się w koszcie CPU i IO. A drugi JOIN jest z małą tabelą miejscowosci, więc nie zajmuje on dużo czasu, a i projektowo ta tabela nie rozrośnie się tak szybko jak meteorogramy, więc nie trzeba tego optymalizować (im mniej indeksów, tym lepiej).