💾 Widoki Zmaterializowane
Tworzenie MV, tryby odświeżania, MV LOG, słowniki danych
- →REFRESH FAST = odświeżanie 'delta' — Oracle zapisuje tylko ZMIANY w specjalnym dzienniku (MV LOG), a przy odświeżeniu stosuje tylko te zmiany zamiast przeliczać wszystko od nowa. Wymaga
CREATE MATERIALIZED VIEW LOG ON tabelana każdej tabeli źródłowej. Bez logu Oracle odmówi wykonania FAST. - →ON COMMIT wymaga REFRESH FAST. Oracle nie pozwala na kombinację REFRESH COMPLETE ON COMMIT — byłoby to zbyt kosztowne (przeliczanie całego MV po każdym COMMIT). Zapamiętaj: ON COMMIT → tylko FAST.
- →ENABLE QUERY REWRITE = transparentna optymalizacja. Oracle sam wykrywa, że zapytanie do tabel bazowych pasuje do istniejącego MV, i zamiast przeliczać — odpytuje MV. Aplikacja nic nie wie i nic nie zmienia.
- →MV LOG pełna składnia:
WITH ROWID, SEQUENCE (kolumna1, kolumna2) INCLUDING NEW VALUES. ROWID = śledzenie aktualizacji, SEQUENCE = kolejność zmian, INCLUDING NEW VALUES = nowe wartości dostępne (wymagane dla SUM FAST). - →staleness w USER_MVIEWS: FRESH = dane aktualne. STALE = tabele źródłowe zmieniły się od ostatniego refresh (MV jest nieaktualny). UNKNOWN = Oracle nie wie czy aktualne.
- →DBMS_MVIEW.REFRESH('nazwa', 'X'): 'C' = Complete (przebuduj od zera), 'F' = Fast (delta, wymaga logu), '?' = Force (F jeśli możliwe, C jeśli nie).
- →GROUP BY w MV z REFRESH FAST wymaga w zapytaniu MV:
COUNT(*)iCOUNT(kolumna)dla każdej funkcji agregującej (SUM, AVG, itd.). Bez tego Fast Refresh nie zadziała. - →START WITH ... NEXT ... = harmonogram automatycznego odświeżania wbudowany w definicję MV.
START WITH SYSDATE NEXT SYSDATE+1= odświeżaj co 24h. Bez tego wymagane jest ręczneDBMS_MVIEW.REFRESH.
Widok zwykły vs zmaterializowany
| Cecha | Widok (VIEW) | Widok zmaterializowany (MV) |
|---|---|---|
| Dane fizyczne | Nie — tylko definicja SQL | Tak — przechowuje wyniki |
| Wydajność | Zawsze przelicza | Szybka (dane na dysku) |
| Odświeżanie | Automatyczne (zawsze aktualne) | Ręczne lub wg harmonogramu |
| Użycie | Abstrakcja, bezpieczeństwo | Cache wyników, hurtownie |
Tworzenie widoku zmaterializowanego
CREATE MATERIALIZED VIEW mv_dept_summary
BUILD IMMEDIATE -- zbuduj od razu (lub: DEFERRED = przy pierwszym odświeżeniu)
REFRESH COMPLETE -- odświeżaj pełnie (lub FAST = delta)
ON DEMAND -- odświeżaj ręcznie (lub ON COMMIT = przy każdym COMMIT)
AS
SELECT d.department_name,
COUNT(e.employee_id) AS liczba_prac,
AVG(e.salary) AS srednia_pensja
FROM hr.employees e
JOIN hr.departments d ON e.department_id = d.department_id
GROUP BY d.department_name;Parametry BUILD
| Opcja | Opis |
|---|---|
BUILD IMMEDIATE | Dane załadowane podczas CREATE (domyślne) |
BUILD DEFERRED | MV jest puste aż do pierwszego odświeżenia |
Parametry REFRESH
| Opcja | Opis | Wymagania |
|---|---|---|
REFRESH COMPLETE | Pełne przepisanie danych (DELETE + INSERT) | Brak — zawsze możliwe |
REFRESH FAST | Delta — tylko zmienione wiersze | Wymaga MV LOG na tabelach źródłowych |
REFRESH FORCE | Próbuj FAST, gdy niemożliwe — COMPLETE | MV LOG jeśli dostępny |
REFRESH NEVER | Nigdy nie odświeżaj automatycznie | — |
Parametry ON
| Opcja | Kiedy odświeżane | Uwaga |
|---|---|---|
ON DEMAND | Tylko ręcznie (DBMS_MVIEW.REFRESH) | Domyślne |
ON COMMIT | Po każdym COMMIT zmieniającym tabele źródłowe | Wymaga REFRESH FAST + MV LOG |
Harmonogram automatycznego odświeżania — START WITH / NEXT
Zamiast ręcznego DBMS_MVIEW.REFRESH możesz zdefiniować harmonogram bezpośrednio w definicji MV.
-- START WITH = kiedy pierwsze odświeżenie (domyślnie: czas tworzenia MV)
-- NEXT = wyrażenie obliczające czas następnego odświeżenia
CREATE MATERIALIZED VIEW sprzedaz_mv
BUILD IMMEDIATE
REFRESH FAST
START WITH SYSDATE
NEXT SYSDATE + 1 -- co 24 godziny
AS
SELECT id_sklepu, id_produktu,
SUM(kwota) AS suma, AVG(kwota) AS srednia
FROM sprzedaz
GROUP BY id_sklepu, id_produktu;
-- Odświeżanie raz w tygodniu (co 7 dni):
REFRESH COMPLETE
START WITH SYSDATE
NEXT SYSDATE + 7
-- Odświeżanie co godzinę:
REFRESH FAST
START WITH SYSDATE
NEXT SYSDATE + 1/24
-- Odświeżanie każdej nocy o 2:00:
REFRESH COMPLETE
START WITH TRUNC(SYSDATE) + 2/24
NEXT TRUNC(SYSDATE) + 1 + 2/24SYSDATE + 1 = co 24 godziny.SYSDATE + 1/24 = co godzinę.TRUNC(SYSDATE) + 1 + 2/24 = jutro o 2:00.Brak START WITH / NEXT = wymagane ręczne odświeżanie (ON DEMAND).
ON COMMIT z REFRESH COMPLETE — pełne odświeżenie przy każdym COMMIT byłoby zbyt kosztowne.ON COMMIT wymaga REFRESH FAST, który wymaga MV LOG.ENABLE QUERY REWRITE — automatyczne przepisywanie zapytań
Oracle może automatycznie przekierować zapytanie do MV zamiast do tabel bazowych — bez zmian w kodzie aplikacji.
CREATE MATERIALIZED VIEW mv_dept_summary
BUILD IMMEDIATE
REFRESH FAST ON COMMIT
ENABLE QUERY REWRITE -- zezwól na automatyczne przepisywanie zapytań
AS
SELECT d.department_name,
COUNT(e.employee_id) AS liczba_prac,
SUM(e.salary) AS suma_pensji
FROM hr.employees e
JOIN hr.departments d ON e.department_id = d.department_id
GROUP BY d.department_name;
-- Oracle automatycznie użyje mv_dept_summary dla tego zapytania:
SELECT department_name, COUNT(employee_id)
FROM hr.employees e JOIN hr.departments d
ON e.department_id = d.department_id
GROUP BY department_name;
-- ^ Nie trzeba pisać "FROM mv_dept_summary" — Oracle przepisuje samQUERY_REWRITE_ENABLED = TRUE (domyślnie TRUE od Oracle 10g).MV musi być FRESH (odświeżony) — przy STALE przepisywanie może być wyłączone.
Jeden MV może obsługiwać wiele różnych zapytań o podobnej strukturze.
Typy widoków zmaterializowanych
| Typ | Charakterystyka | Wymagania |
|---|---|---|
| Agregujący | Zawiera GROUP BY + agregaty (COUNT, SUM, AVG) | COUNT(*) wymagany przy agregatach; MV LOG musi zawierać używane kolumny |
| Join-only | Złączenie tabel bez agregacji | MV LOG na każdej tabeli; ROWID w MV LOG |
| Zagnieżdżony (nested) | MV oparty na innym MV | Bazowy MV musi istnieć; odświeżanie w odpowiedniej kolejności |
-- MV agregujący (najczęstszy):
CREATE MATERIALIZED VIEW mv_agg
BUILD IMMEDIATE REFRESH FAST ON COMMIT
AS SELECT department_id, COUNT(*) cnt, SUM(salary) suma
FROM hr.employees GROUP BY department_id;
-- MV join-only (bez GROUP BY):
CREATE MATERIALIZED VIEW mv_join
BUILD IMMEDIATE REFRESH FAST ON DEMAND
AS SELECT e.employee_id, e.last_name, d.department_name
FROM hr.employees e JOIN hr.departments d
ON e.department_id = d.department_id;
-- MV zagnieżdżony (bazuje na mv_agg):
CREATE MATERIALIZED VIEW mv_nested
BUILD IMMEDIATE REFRESH COMPLETE ON DEMAND
AS SELECT department_id, suma / cnt AS sr_pensja
FROM mv_agg;Obiekty tworzone przez Oracle przy CREATE MATERIALIZED VIEW
| Obiekt | Nazwa | Opis |
|---|---|---|
| Tabela kontenera | Taka sama jak MV | Fizyczna tabela przechowująca dane MV |
| Definicja widoku | Taka sama jak MV | Widok SQL używany do odświeżania |
| Indeks (MV agregujący) | Automatyczna nazwa | Indeks na kolumnach GROUP BY dla REFRESH FAST |
MV LOG — log zmian dla REFRESH FAST
MV LOG śledzi zmiany w tabeli źródłowej. Istnieją 3 warianty identyfikacji wierszy:
| Opcja | Co zapisuje | Kiedy |
|---|---|---|
WITH PRIMARY KEY | Wartości klucza głównego | Tabela ma PK; domyślne |
WITH ROWID | Fizyczny adres wiersza (ROWID) | Brak PK lub join-only MV |
WITH ROWID, PRIMARY KEY | Oba: ROWID i PK | Dla elastyczności i wydajności |
WITH SEQUENCE | Kolejność zmian (timestamp) | Zawsze razem z PRIMARY KEY lub ROWID |
-- Utwórz log zmian na tabeli źródłowej:
CREATE MATERIALIZED VIEW LOG ON hr.employees
WITH ROWID, SEQUENCE (employee_id, salary, department_id)
INCLUDING NEW VALUES;
-- Dopiero teraz możesz tworzyć MV z REFRESH FAST:
CREATE MATERIALIZED VIEW mv_fast_refresh
BUILD IMMEDIATE
REFRESH FAST ON COMMIT
AS
SELECT employee_id, salary, department_id
FROM hr.employees;2. MV LOG musi zawierać wszystkie kolumny używane w MV
3. Zapytanie MV musi spełniać ograniczenia (np. GROUP BY z COUNT(*), agregaty)
4. Przy agregacji: MV musi zawierać COUNT(*) i COUNT(kol) dla każdej agregowanej kolumny
Ręczne odświeżanie
-- Odśwież konkretny widok:
EXEC DBMS_MVIEW.REFRESH('mv_dept_summary');
-- Odśwież z trybem:
EXEC DBMS_MVIEW.REFRESH('mv_dept_summary', 'C'); -- C = Complete
EXEC DBMS_MVIEW.REFRESH('mv_dept_summary', 'F'); -- F = Fast
EXEC DBMS_MVIEW.REFRESH('mv_dept_summary', '?'); -- ? = Force
-- Odśwież wiele widoków naraz:
EXEC DBMS_MVIEW.REFRESH_ALL_MVIEWS();
-- Odśwież listę widoków:
EXEC DBMS_MVIEW.REFRESH(LIST => 'mv1,mv2,mv3', METHOD => 'C');Słowniki danych — przydatne widoki
-- Lista widoków zmaterializowanych bieżącego użytkownika:
SELECT mview_name, refresh_mode, refresh_method, last_refresh_date, staleness
FROM user_mviews;
-- staleness: FRESH (aktualne), STALE (nieaktualne), UNKNOWN
-- Wszystkie MV w bazie (DBA):
SELECT owner, mview_name, refresh_mode, last_refresh_date
FROM dba_mviews;
-- Logi MV:
SELECT log_table, log_trigger, rowids, sequence, primary_key
FROM user_mview_logs;
-- Historia czasów odświeżania MV:
SELECT name, last_refresh
FROM user_mview_refresh_times;
-- Komentarze do MV:
SELECT mview_name, comments
FROM user_mview_comments;ALTER MATERIALIZED VIEW
-- Zmiana trybu odświeżania:
ALTER MATERIALIZED VIEW mv_dept_summary
REFRESH COMPLETE ON DEMAND;
-- Włączenie/wyłączenie QUERY REWRITE:
ALTER MATERIALIZED VIEW mv_dept_summary ENABLE QUERY REWRITE;
ALTER MATERIALIZED VIEW mv_dept_summary DISABLE QUERY REWRITE;
-- Zmiana na BUILD DEFERRED (kompilacja bez ładowania):
ALTER MATERIALIZED VIEW mv_dept_summary
REFRESH COMPLETE ON DEMAND;
-- Skompiluj MV po zmianie struktury bazowych tabel:
ALTER MATERIALIZED VIEW mv_dept_summary COMPILE;COMMENT ON MATERIALIZED VIEW
-- Dodaj komentarz do MV:
COMMENT ON MATERIALIZED VIEW mv_dept_summary IS 'Podsumowanie departamentów — odświeżaj raz dziennie';
-- Odczytaj komentarze (z ALL_MVIEW_COMMENTS lub USER_MVIEW_COMMENTS):
SELECT mview_name, comments
FROM user_mview_comments
WHERE mview_name = 'MV_DEPT_SUMMARY';Przywileje wymagane do tworzenia MV
| Przywilej | Do czego |
|---|---|
CREATE MATERIALIZED VIEW | Tworzenie MV we własnym schemacie |
CREATE ANY MATERIALIZED VIEW | Tworzenie MV w cudzym schemacie |
CREATE TABLE | Wymagany — MV tworzy tabelę kontenera |
QUERY REWRITE | ENABLE QUERY REWRITE we własnym schemacie |
GLOBAL QUERY REWRITE | ENABLE QUERY REWRITE na tabelach innych schematów |
Usuwanie MV i LOG
-- Usuń widok zmaterializowany:
DROP MATERIALIZED VIEW mv_dept_summary;
-- Usuń log (najpierw sprawdź czy nie ma MV korzystającego z FAST):
DROP MATERIALIZED VIEW LOG ON hr.employees;Pytania egzaminacyjne
Które połączenie parametrów jest NIEPOPRAWNE dla widoku zmaterializowanego?
Co jest wymagane do użycia REFRESH FAST?
Co robi ENABLE QUERY REWRITE?
Pole 'staleness' w USER_MVIEWS = 'STALE' oznacza...
CREATE MATERIALIZED VIEW mv_test
BUILD IMMEDIATE
REFRESH FAST ON COMMIT
AS SELECT employee_id, salary FROM hr.employees;EXEC DBMS_MVIEW.REFRESH('MV_DEPT_SUMMARY', 'C');DROP MATERIALIZED VIEW LOG ON hr.employees;Ćwiczenia
Ćwiczenie 1 — Podstawowy MV
Utwórz widok zmaterializowany mv_salary_stats, który dla każdego działu pokazuje: department_id, liczbę pracowników, średnią pensję, minimalną i maksymalną pensję. Użyj BUILD IMMEDIATE, REFRESH COMPLETE ON DEMAND.
Ćwiczenie 2 — MV LOG i REFRESH FAST
Utwórz MV LOG na hr.employees dla kolumn employee_id, salary, department_id. Następnie stwórz widok zmaterializowany mv_emp_fast z REFRESH FAST ON COMMIT, który wybiera te kolumny.
Ćwiczenie 3 — Słowniki i odświeżanie
Wyświetl wszystkie widoki zmaterializowane bieżącego użytkownika (nazwa, tryb odświeżenia, data ostatniego odświeżenia). Następnie ręcznie odśwież mv_salary_stats metodą COMPLETE.