Optymalizacja projektu bazy danych

Pierwotny projekt bazy

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:

Projekt bazy po optymalizacji

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).