Kiedy używać dziedziczonych tabel w PostgreSQL?

84

W jakich sytuacjach należy używać dziedziczonych tabel? Próbowałem ich używać bardzo krótko i dziedziczenie nie wyglądało jak w świecie OOP.

Myślałem, że to działa tak:

Tabela userszawierająca wszystkie pola wymagane na wszystkich poziomach użytkownika. Stoły podoba moderators, admins, bloggersitp ale pola są nie sprawdzone od rodzica. Na przykład usersma pole e-mail i dziedziczone bloggersma je teraz, ale nie jest unikalne dla obu usersi bloggersw tym samym czasie. to znaczy. tak samo jak dodaję pole e-mail do obu tabel.

Jedyne użycie, o którym mogłem pomyśleć, to pola, które są zwykle używane, takie jak row_is_deleted , created_at , modified_at . Czy to jedyne użycie dla dziedziczonych tabel?

raspi
źródło

Odpowiedzi:

111

Istnieje kilka głównych powodów używania dziedziczenia tabel w Postgres.

Powiedzmy, że mamy kilka tabel potrzebnych do statystyk, które są tworzone i wypełniane co miesiąc:

statistics
    - statistics_2010_04 (inherits statistics)
    - statistics_2010_05 (inherits statistics)

W tym przykładzie mamy 2 000 000 wierszy w każdej tabeli. Każda tabela ma ograniczenie typu CHECK, aby mieć pewność, że zostaną w niej zapisane tylko dane z odpowiedniego miesiąca.

Co więc sprawia, że ​​dziedziczenie jest fajną funkcją - dlaczego dzielenie danych jest fajne?

  • WYDAJNOŚĆ: Wybierając dane, WYBIERAMY * FROM statystyki WHERE data BETWEEN x i Y, a Postgres korzysta z tabel tylko tam, gdzie ma to sens. Na przykład. WYBIERZ * FROM statystyki GDZIE data MIĘDZY „2010-04-01” a „2010-04-15” skanuje tylko tabelę statistics_2010_04, wszystkie inne tabele nie zostaną dotknięte - szybko!
  • Rozmiar indeksu: nie mamy dużej tabeli tłuszczu z dużym indeksem tłuszczu według daty kolumny. Mamy małe tabele miesięcznie, z małymi indeksami - szybsze odczyty.
  • Konserwacja: Możemy uruchomić próżnię, reindeks, klaster na każdej tabeli miesiąca bez blokowania wszystkich innych danych

Aby uzyskać informacje na temat prawidłowego używania dziedziczenia tabeli jako wzmacniacza wydajności, zapoznaj się z podręcznikiem postgresql. Musisz ustawić ograniczenia CHECK dla każdej tabeli, aby poinformować bazę danych, według którego klucza dane zostaną podzielone (podzielone na partycje).

W dużym stopniu korzystam z dziedziczenia tabel, zwłaszcza jeśli chodzi o przechowywanie danych dziennika pogrupowanych według miesięcy. Wskazówka: jeśli przechowujesz dane, które nigdy się nie zmienią (loguj dane), utwórz lub indeksuj za pomocą CREATE INDEX ON () WITH (fillfactor = 100); Oznacza to, że w indeksie nie zostanie zarezerwowane miejsce na aktualizacje - indeks na dysku jest mniejszy.

AKTUALIZACJA: domyślny współczynnik wypełnienia to 100, z http://www.postgresql.org/docs/9.1/static/sql-createtable.html :

Współczynnik wypełnienia tabeli wynosi od 10 do 100 procent. 100 (pełne opakowanie) jest wartością domyślną

S38
źródło
13
Kolejny przykład rozstania
Frank Heikens
4
W Twoim punkcie 1, w jaki sposób Postgres rozumie, które tabele są potrzebne do wyszukiwania? Wybierasz z tabeli nadrzędnej, a zakres dat jest tylko wygodnym przykładem podziału. Tabela nadrzędna nie może znać tej logiki. Albo nie mam racji?
Alexander Palamarchuk
4
Wykonywanie zapytania w tabeli nadrzędnej jest w rzeczywistości takie samo, jak wykonywanie zapytania na UNION ALL w każdej tabeli podrzędnej we wspólnych wierszach. Planista zapytań jest świadomy ograniczeń sprawdzania, które definiują każdą partycję i jeśli nie nakładają się one na partycje, używa ich do określenia, że ​​może pominąć sprawdzanie tabel, dla których kontrolki CHECK wskazują, że nie zostaną zwrócone żadne wiersze. Postgres dokumentuje to
zxq9
@avesus heh ... Powyższy kod wzięty sam w sobie jest godny takiego sarkazmu. Typowe jest zawijanie tego rodzaju rzeczy do jakiejś rutyny konserwacji. Może to być tak proste, jak procedura składowana, która zajmuje się tym pod pewnymi warunkami, zadaniem crona lub czymkolwiek. Powszechne jest dzielenie według daty, ale od czasu do czasu zdarzyło mi się również dzielić na partycje według alokacji obszaru tabel, a to wymaga pewnych informacji zewnętrznych - 30 minut potrzebnych na napisanie opiekunki do partycji jest tego warte dla kontroli To daje Ci.
zxq9
Hmm. Czy na pewno nie blokuje? Mam podobną konfigurację, ale kiedy uruchamiam polecenie CLUSTER na jednej partycji, instrukcja SELECT dotycząca danych przechowywanych przez inne bloki partycji!
E. van Putten,
37

„Dziedziczenie tabeli” oznacza coś innego niż „dziedziczenie klas” i służą one innym celom.

Postgres dotyczy definicji danych. Czasami naprawdę złożone definicje danych. OOP (w typowym znaczeniu dla języka Java) polega na podporządkowaniu zachowań definicjom danych w pojedynczej strukturze atomowej. Cel i znaczenie słowa „dziedziczenie” są tutaj znacząco inne.

W krainie OOP mogę zdefiniować (będąc tutaj bardzo luźnym ze składnią i semantyką):

import life

class Animal(life.Autonomous):
  metabolism = biofunc(alive=True)

  def die(self):
    self.metabolism = False

class Mammal(Animal):
  hair_color = color(foo=bar)

  def gray(self, mate):
    self.hair_color = age_effect('hair', self.age)

class Human(Mammal):
  alcoholic = vice_boolean(baz=balls)

Tabele do tego mogą wyglądać następująco:

CREATE TABLE animal
  (name       varchar(20) PRIMARY KEY,
   metabolism boolean NOT NULL);

CREATE TABLE mammal
  (hair_color  varchar(20) REFERENCES hair_color(code) NOT NULL,
   PRIMARY KEY (name))
  INHERITS (animal);

CREATE TABLE human
  (alcoholic  boolean NOT NULL,
   FOREIGN KEY (hair_color) REFERENCES hair_color(code),
   PRIMARY KEY (name))
  INHERITS (mammal);

Ale gdzie są zachowania? Nigdzie nie pasują. Nie jest to celem „obiektów”, ponieważ są one omawiane w świecie baz danych, ponieważ bazy danych dotyczą danych, a nie kodu proceduralnego. Możesz zapisać funkcje w bazie danych, aby wykonać obliczenia za Ciebie (często bardzo dobry pomysł, ale nie jest to coś, co pasuje do tego przypadku), ale funkcje to nie to samo co metody - metody rozumiane w postaci OOP, o którym mówisz około są celowo mniej elastyczne.

Jest jeszcze jedna rzecz, na którą należy zwrócić uwagę, jeśli chodzi o dziedziczenie jako urządzenie schematyczne: od Postgres 9.2 nie ma możliwości odniesienia się do ograniczenia klucza obcego dla wszystkich członków rodziny partycji / tabel jednocześnie. Możesz napisać kontrole, aby to zrobić, lub obejść to w inny sposób, ale nie jest to wbudowana funkcja (sprowadza się do problemów ze złożonym indeksowaniem, a nikt nie napisał bitów niezbędnych do wykonania tego automatycznego). Zamiast używać dziedziczenia tabel do tego celu, często lepszym rozwiązaniem w bazie danych dla dziedziczenia obiektów jest tworzenie schematycznych rozszerzeń tabel. Coś takiego:

CREATE TABLE animal
  (name       varchar(20) PRIMARY KEY,
   ilk        varchar(20) REFERENCES animal_ilk NOT NULL,
   metabolism boolean NOT NULL);

CREATE TABLE mammal
  (animal      varchar(20) REFERENCES animal PRIMARY KEY,
   ilk         varchar(20) REFERENCES mammal_ilk NOT NULL,
   hair_color  varchar(20) REFERENCES hair_color(code) NOT NULL);


CREATE TABLE human
  (mammal     varchar(20) REFERENCES mammal PRIMARY KEY,
   alcoholic  boolean NOT NULL);

Teraz mamy odwołanie kanoniczne do instancji zwierzęcia, którego możemy niezawodnie użyć jako odniesienia do klucza obcego, i mamy kolumnę „podobną”, która odwołuje się do tabeli definicji xxx_ilk, która wskazuje na „następną” tabelę danych rozszerzonych ( lub wskazuje, że nie ma żadnego, jeśli podobny jest sam typem ogólnym). Pisanie funkcji tabel, widoków itp. W tego rodzaju schemacie jest tak łatwe, że większość frameworków ORM robi dokładnie tego rodzaju rzeczy w tle, kiedy uciekasz się do dziedziczenia klas w stylu OOP, aby utworzyć rodziny typów obiektów.

zxq9
źródło
Co by było, gdybyś dodał wszystkie znane ssaki? Czy odziedziczyłbyś po ssaku czy miałbyś klucz obcy, tak jak tutaj? Problem, który mam z kluczami obcymi polega na tym, że w końcu musisz wykonać tak wiele złączeń.
puk
2
@puk Najpierw musisz zdecydować, dlaczego dodajesz każdego znanego ssaka. Kształt danych zostanie określony przez sposób, w jaki dane zostaną wykorzystane (prawdopodobnie nie jest konieczne posiadanie tabeli dla każdego zwierzęcia w tym przypadku - rozważ bazy danych dla bestiariuszy gier, w których naprawdę masz każdy rodzaj moba ). W powyższym przypadku zwykle dodałbym widok, który jest najczęstszym przypadkiem mammal JOIN humantylko dlatego, że pisanie złączenia za każdym razem jest denerwujące. Ale nie unikaj połączeń . Połączenia są tym, co umieszcza R w RDBMS. Jeśli nie lubisz łączenia, powinieneś użyć innego typu bazy danych.
zxq9
@ zxq9: Zgaduję, że masowe, nieefektywne łączenia z powodu dużych tabel są tam, gdzie w grę wchodzą zmaterializowane widoki? (Od tak dawna nie używałem Postgres)
Mark K Cowan,
2
@MarkKCowan Łączenia nie są nieefektywne. To, co jest nieefektywne, to próba dołączenia na nieindeksowanych, nieunikalnych polach (ponieważ schemat nie jest nigdzie bliski znormalizowania) z powodu niechlujnego projektu. W takich przypadkach pomocny może być zmaterializowany pogląd. Widoki zmaterializowane są również pomocne w przypadku, gdy potrzebujesz znormalizowanych danych jako podstawy schematu (często prawdziwe), ale potrzebujesz również kilku działających, zdenormalizowanych reprezentacji, z którymi łatwiej jest pracować, zarówno ze względu na wydajność przetwarzania (ładowanie obliczeń od przodu), jak i wydajność poznawczą. Jeśli więcej piszesz niż czytasz, jest to jednak pesymizacja.
zxq9
2
@MarkKCowan „Slow” to termin względny. Z mojego doświadczenia wynika, że ​​w dużych systemach biznesowych i serwerach gier, w których możemy zaakceptować ~ 50 ms, aby zwrócić zapytanie, połączenia 20 tabel nigdy nie stanowiły problemu (w każdym razie w Postgres 8+). Ale w przypadkach, w których kierownictwo chce <1 ms odpowiedzi na> 10b łączenia wierszy w ponad 5 tabelach na danych nieindeksowanych (lub wartościach pochodnych!) ... żaden system na świecie nie będzie czuł się „szybko” poza dołączeniem tego połączenia w zeszłym miesiącu i przechowywaniem go w szybkim sklepie K / V (co jest zasadniczo tym, co zmaterializowany widok może działać w szczególnych okolicznościach). Nie można uniknąć kompromisu w czasie zapisu lub odczytu.
zxq9
6

Dziedziczenie może być używane w paradygmacie OOP, o ile nie ma potrzeby tworzenia kluczy obcych w tabeli nadrzędnej. Na przykład, jeśli masz pojazd klasy abstrakcyjnej przechowywany w tabeli pojazdów i samochód stołowy, który dziedziczy po nim, wszystkie samochody będą widoczne w tabeli pojazdu, ale klucz obcy z tabeli kierowcy na stole pojazdu nie będzie pasował do tych dokumentacja.

Dziedziczenie może być również używane jako narzędzie do partycjonowania . Jest to szczególnie przydatne, gdy masz tabele, które mają rosnąć wiecznie (tabele dziennika itp.).

grégoire hubert
źródło
1
Ograniczenia tabeli nie są dziedziczone, więc to coś więcej niż tylko klucze obce. Możesz zastosować ograniczenia tabel do tabel podrzędnych, tak jak są one tworzone w Twoim DDL, lub możesz napisać wyzwalacze, które będą obowiązywały te same ograniczenia.
Wexxor,
3

Dziedziczenie jest głównie używane do partycjonowania, ale czasami jest to przydatne w innych sytuacjach. W mojej bazie danych jest wiele tabel różniących się tylko kluczem obcym. Obraz mojej tabeli „klasy abstrakcyjnej” zawiera identyfikator (klucz podstawowy musi znajdować się w każdej tabeli) i raster PostGIS 2.0. Dziedziczone tabele, takie jak „site_map” lub „artifact_drawing”, mają kolumnę z kluczem obcym (kolumna tekstowa „site_name” dla „site_map”, kolumna „artifact_id” z liczbą całkowitą dla tabeli „artifact_drawing” itp.) Oraz ograniczenia klucza podstawowego i obcego; reszta jest dziedziczona z tabeli „image”. Podejrzewam, że w przyszłości będę musiał dodać kolumnę „opis” do wszystkich tabel obrazów, więc zaoszczędzi mi to sporo pracy bez robienia prawdziwych problemów (cóż,

EDYCJA: kolejne dobre zastosowanie: przy dwóch tabelach obsługi niezarejestrowanych użytkowników , inne RDBMS mają problemy z obsługą dwóch tabel, ale w PostgreSQL jest to łatwe - po prostu dodaj, ONLYgdy nie jesteś zainteresowany danymi w odziedziczonej tabeli "niezarejestrowanych użytkowników".

Paweł V.
źródło
2

Jedyne doświadczenie, jakie mam z dziedziczonymi tabelami, dotyczy podziału na partycje. Działa dobrze, ale nie jest to najbardziej wyrafinowana i łatwa w użyciu część PostgreSQL.

W zeszłym tygodniu szukaliśmy tego samego problemu OOP, ale mieliśmy zbyt wiele problemów z Hibernacją (nie podobała nam się nasza konfiguracja), więc nie używaliśmy dziedziczenia w PostgreSQL.

Frank Heikens
źródło
0

Dziedziczenie używam, gdy mam więcej niż 1 na 1 relacji między tabelami.

Przykład: załóżmy, że chcesz przechowywać lokalizacje map obiektów z atrybutami x, y, obrót, skalę.

Załóżmy teraz, że masz kilka różnych rodzajów obiektów do wyświetlenia na mapie, a każdy obiekt ma własne parametry lokalizacji na mapie, a parametry mapy nigdy nie są ponownie używane.

W takich przypadkach dziedziczenie tabel byłoby całkiem przydatne, aby uniknąć konieczności utrzymywania nienormalizowanych tabel lub tworzenia identyfikatorów lokalizacji i odwoływania się do innych tabel.

Maarten
źródło
-4

Używaj go jak najmniej. A to zwykle oznacza nigdy, sprowadza się do sposobu tworzenia struktur, które naruszają model relacyjny, na przykład poprzez łamanie zasady informacji i tworzenie worków zamiast relacji.

Zamiast tego użyj partycjonowania tabel połączonego z odpowiednim modelowaniem relacyjnym, w tym innymi formami normalnymi.

Leandro
źródło
4
Nie jest prawdą, że funkcja dziedziczenia PostgreSQL narusza model relacyjny, łamiąc zasadę informacji. Zasada informacji mówi, że wszystkie dane w relacyjnej bazie danych są reprezentowane przez wartości danych w relacjach, a wszystkie wyniki zapytań są ponownie reprezentowane jako relacja. ( En.wikipedia.org/wiki/Relational_model ) Tak jest zawsze, ponieważ wszystkie tabele , które dziedziczą inną tabelę, są znowu prostymi tabelami. Z tego powodu nie ma czegoś takiego jak „torba”, cokolwiek to znaczy.
Roland,
2
Cóż, Wikipedia nie stanowi odniesienia, jeśli chodzi o model relacyjny; odmawia uznania, że ​​SQL narusza model relacyjny. Torba jest stołem bez klucza, ponieważ potencjalnie ma duplikaty, a więc nie jest relacją; relacja musi być zbiorem.
Leandro
To nie jest problem samej funkcji, ale sposobu jej wykorzystania. Jeśli pracujesz z uuidami jako identyfikatorami, będziesz mieć unikalne klucze we wszystkich tabelach podrzędnych.
Roland
Masz rację, ale problem polega na tym, że dziedziczenie prowadzi modelarza do ignorowania modelu relacyjnego. UUID nie są prawdziwymi kluczami, ale kluczami zastępczymi. Trzeba jeszcze zadeklarować klucze naturalne.
Leandro