Wiersze w tabelach bazy danych Oracle zorganizowane są w “listę” gdzie każdy rekord ma swój unikalny adres w obrębie całej bazy danych. Adres ten to ROWID który definiuje dokładne miejsce przechowywania rekordu w obszarze bazy danych. Dzięki temu, że ROWID przechowuje bezpośredni adres rekordu w bazie danych jest możliwe bardzo szybkie jego odnalezienie.
Ten kurs jest częścią Kurs Oracle SQL
Adres rekordu ROWID składa się z czterech części. Adres rekordu dla bazy danych Oracle zawiera informacje niezbędne do zlokalizowania wiersza takie jak:
- Numer obiektu danych
- Plik danych, w którym znajduje się wiersz (pierwszy plik to 1). Numer pliku odnosi się do przestrzeni tabel.
- Pozycja wiersza w bloku danych (pierwszy wiersz to 0)
- Blok danych w pliku danych, w którym znajduje się wiersz
W Oracle rowid przechowywany jest jako wartość base64, która może zawierać znaki A-Z, a-z, 0-9 oraz znak plus (+) i ukośnik (/). Technicznie cały adres ROWID składa się z 10 bajtów w formacie OOOOOOFFFBBBBBBRRR
- bity od 1 do 32 (bajty od 1 do 4): identyfikator obiektu danych (0-4294967295)
- bity od 33 do 44 (bajt 5 i pół bajtu 6): numer pliku w przestrzeni tabel (0-4095)
- bity od 45 do 64 (pół bajtu 6 oraz bajty 7 i 8): numer bloku w pliku (0-1048575)
- bity od 65 do 80 (bajty 9 i 10): numer wiersza wewnątrz bloku (0-65535)
Adres w ramach bazy danych
Aby pokazać jak wyciągnąć poszczególne dane z ROWID oraz przeprowadzić testy przygotujmy tabelę:
CREATE TABLE TEST_TABLE (
ID NUMBER GENERATED ALWAYS AS IDENTITY
, FOO VARCHAR2(255)
, BAR NUMBER
, CONSTRAINT ID_PK PRIMARY KEY (ID)
);
Z danymi:
BEGIN
FOR I IN 0 .. 100000
LOOP
INSERT INTO TEST_TABLE (
FOO
, BAR
) VALUES (
'ACME: ' || MOD(I, 10)
, MOD(I, 100)
);
END LOOP;
END;
/
COMMIT;
Aby wyciągnąć adres ROWID z rekordu należy do zapytania SELECT dodać pesudokolumnę ROWID tak jak na poniższym przykładzie:
SELECT
ROWID
, ID
, FOO
,BAR
FROM TEST_TABLE;
Oraz wynik
W powyższym wyniku zapytania widać wartości pseudokolumny ROWID. Widać, jak kolejno zwiększa się ta wartość, ale u was wcale nie musi tak być. Wynika to z faktu, że załadowane dane były do pustych bloków w związku z tym rekordy zapisywane były po kolei. Ważną informacją jest to, że ROWID nie jest fizycznie przechowywane w bazie danych dlatego też, że nie można go dodawać, zmieniać czy usuwać.
Skoro ROWID jest bezpośrednim adresem rekordu w bazie to wyszukanie go powinno być odpowiednio szybkie. Zobaczmy więc plan zapytania dla wyszukania rekordu po ROWID tak jak w poniższym zapytaniu:
SELECT
ROWID
, ID
, FOO
,BAR
FROM TEST_TABLE
WHERE 1 = 1
AND ROWID = 'AAATisAABAAAMktAAO';
Oraz plan zapytania:
W planie zapytania widzimy dostęp o koszcie 1 co oznacza, że dostęp będzie odpowiednio szybki. TABLE ACCESS BY USER ROWID oznacza, że baza wykona przeszukanie po tabeli bezpośrednio po ROWID podanym przez USERa. Dostęp do danych przy użyciu rowid jest bardzo szybki ponieważ baza bezpośrednio odnajduje blok danych a dalej rekord którego poszukujemy.
Szukając właściwości ROWID postarajmy się więc zdekodować i odnaleźć gdzie znajduje się rekord testowego adresu czyli w moim przypadku AAATisAABAAAMktAAO. Do operacji na rowid służy pakiet DBMS_ROWID (dokumentacja). Pakiet ten pozwala między innymi rozkodować rekord przy użyciu odpowiednich funkcji tak jak w przykładzie poniżej:
SELECT
DBMS_ROWID.ROWID_OBJECT('AAATisAABAAAMktAAO') AS "NUMER_OBIEKTU"
, DBMS_ROWID.ROWID_RELATIVE_FNO('AAATisAABAAAMktAAO') AS "NUMER_PLIKU"
, DBMS_ROWID.ROWID_BLOCK_NUMBER('AAATisAABAAAMktAAO') AS "NR_BLOKU_DANYCH"
, DBMS_ROWID.ROWID_ROW_NUMBER('AAATisAABAAAMktAAO') AS "NR_REKORDU"
FROM dual
;
Widzimy już zdekodowane nr obiektu, pliku, bloku danych oraz rekordu w ramach bloku. Zobaczmy więc czy faktycznie tak jest. A więc sprawdźmy w pierwszej kolejności nr obiektu w ALL_OBJECTS:
SELECT
*
FROM ALL_OBJECTS
WHERE 1 = 1
AND OBJECT_ID = 80044;
Powyższy wynik pokazuje szczegóły obiektu o object_id = 80044. Jest to TEST_TABLE co się zgadza ponieważ tą tabelę wcześniej utworzyliśmy i testowy rekord z niej pochodzi. Przy okazji widać, że tabelę utworzyłem użytkownikiem sys czego w normalnych(nie dydaktycznych) warunkach nie polecam 🙂
Kolejną częścią ROWID jest adres pliku. Aby go zobaczyć należy zajrzeć do DBA_DATA_FILES tak jak w poniższym zapytaniu:
SELECT
*
FROM DBA_DATA_FILES
WHERE 1 = 1
AND RELATIVE_FNO = 1
Jak widzimy, nasza tabela znajduje się w TABLESPACE SYSTEM oraz w pliku o nazwie system01.dbf. Adres pliku w rowid przechowuje RELATIVE_FNO.
Aby zobaczyć gdzie znajduje się blok danych zobaczmy wynik z tabeli DBA_EXTENTS:
SELECT
*
FROM DBA_EXTENTS
WHERE 1 = 1
AND SEGMENT_NAME = 'TEST_TABLE'
AND BLOCK_ID < 51501
AND BLOCK_ID > 51501 - BLOCKS;
Oraz wynik
Nasz rekord znajduje się w bloku o numerze 50501 który to znajduje się w extent o id 16. Extenty to zbiory bloków i ich BLOCK_ID oznacza ID pierwszego bloku danych natomiast ostatni to BLOCK_ID + BLOCKS. Kolumna BLOCKS określa ile bloków znajduje się w jednym EXTENT. Dlatego też zapytanie przeszukuje zakresy w extendach.
Należałoby jeszcze sprawdzić w takim razie jak wygląda pozycja rekordu w bloku danych. Wiemy, że jest to pozycja 14 jednak Oracle nie udostępnia podglądu samego bloku więc jedyne co nam pozostaje to domniemywać, że dokumentacja nie kłamie 🙂
Uwagi dodatkowe
Istnieje jeszcze jeden specjalny “ROWID” który występuje w tabeli zorganizowanej w index. Adres rekordu w takiej tabeli to UROWID. O szczegółach UROWID i tabelach zorganizowanych w index zamieszczę inny kurs 🙂
ROWID nie powinniśmy nigdy traktować jako klucza głównego czy obcego ponieważ może okazać się, że pod zadanym rowid rekordu już nie ma. Dzieje się tak, ponieważ rekordy mogą zmieniać miejsce np. zmieniając blok. W konsekwencji ten sam rekord ma inny rowid. O tym w innym kursie 🙂
Podsumowanie
- ROWID jest adresem rekordu w bazie danych
- Dostęp do rekordu tabeli poprzez ROWID jest bardzo szybki
- ROWID jest unikatowym identyfikatorem wiersza w obrębie całej bazy danych (z wyłączeniem tabel klastrze)
- Nie powinno się używać ROWID jako ID ponieważ możliwa jest zmiana ROWID poprzez np. delete oraz insert rekordu z tymi samymi wartościami.
Rozwinięciem wątku o blokach powinien być imho jeszcze wpis o High Water Mark, co to jest, jak się zachowuje baza, gdy dane zostaną usunięte lub poddane truncate.
Jak HWM wpływa na optymalizację (po usunięciu danych, puste bloki plików też są przeglądane np. przy full scanie) i jak go zresetować, aby puste bloki były pomijane.
Cześć,
Właśnie dodałem wpis o HWM pod adresem: https://oracledev.pl/oracle-high-water-mark-a-truncate-i-delte/
Cześć,
nie rozumiem tego zdania: “Nasz rekord znajduje się w bloku o numerze 50501 który to znajduje się w extent o id 16. Extenty to zbiory bloków i ich BLOCK_ID oznacza ID pierwszego bloku danych natomiast ostatni to BLOCK_ID + BLOCKS. Kolumna BLOCKS określa ile bloków znajduje się w jednym EXTENT. Dlatego też zapytanie przeszukuje zakresy w extendach.” Czy mógłbyś to trochę lepiej wytłumaczyć, bo nie widzę po prostu tego…?
Zobacz na przykład o którym jest mowa.
ROWID wskazuje, że rekord znajduje się w bloku o numerze: 51501. EXTENT który wskazałem w przykładzie to zbiór bloków od 51456 do 51456+128. Widać więc, że blok 51501 znajduje się w tym zakresie a dokładniej to blok 51501 który jest wskazany przez ROWID znajduje się w tym EXTENT.