Quando utilizzare le tabelle ereditate in PostgreSQL?


84

In quali situazioni dovresti usare le tabelle ereditate? Ho provato a usarli molto brevemente e l'ereditarietà non sembrava nel mondo OOP.

Ho pensato che funzionasse così:

Tabella usersche contiene tutti i campi richiesti per tutti i livelli utente. Tavoli come moderators, admins, bloggers, ecc, ma i campi sono non controllati dai genitori. Ad esempio, usersha un campo email e bloggerslo ha ereditato anche ora, ma non è unico per entrambi userse bloggersallo stesso tempo. cioè. lo stesso che aggiungo il campo email a entrambe le tabelle.

L'unico utilizzo a cui potrei pensare sono i campi che vengono solitamente utilizzati, come row_is_deleted , created_at , modified_at . Questo è l'unico utilizzo per le tabelle ereditate?

Risposte:


111

Ci sono alcune ragioni principali per usare l'ereditarietà delle tabelle in postgres.

Diciamo che abbiamo alcune tabelle necessarie per le statistiche, che vengono create e riempite ogni mese:

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

In questo esempio, abbiamo 2.000.000 di righe in ogni tabella. Ogni tabella ha un vincolo CHECK per assicurarsi che vengano memorizzati solo i dati per il mese corrispondente.

Quindi cosa rende l'ereditarietà una caratteristica interessante: perché è interessante dividere i dati?

  • PRESTAZIONI: Quando si selezionano i dati, SELEZIONIAMO * DALLE statistiche DOVE data TRA xe Y, e Postgres utilizza solo le tabelle, dove ha senso. Per esempio. SELEZIONA * DALLE statistiche DOVE la data TRA '2010-04-01' E '2010-04-15' scansiona solo la tabella statistics_2010_04, tutte le altre tabelle non verranno toccate - velocemente!
  • Dimensione dell'indice: non abbiamo una grande tabella di grasso con un grande indice di grasso alla data della colonna. Abbiamo piccole tabelle al mese, con piccoli indici - letture più veloci.
  • Manutenzione: possiamo eseguire il vuoto completo, reindicizzare, raggruppare su ogni tabella mensile senza bloccare tutti gli altri dati

Per il corretto utilizzo dell'ereditarietà delle tabelle come potenziamento delle prestazioni, consultare il manuale postgresql. È necessario impostare i vincoli CHECK su ciascuna tabella per indicare al database, su quale chiave vengono suddivisi (partizionati) i dati.

Faccio un uso massiccio dell'ereditarietà delle tabelle, soprattutto quando si tratta di archiviare i dati di registro raggruppati per mese. Suggerimento: se si archiviano dati, che non cambieranno mai (dati di registro), creare o indicizzare con CREATE INDEX ON () WITH (fillfactor = 100); Ciò significa che nessuno spazio per gli aggiornamenti sarà riservato nell'indice: l'indice è più piccolo sul disco.

AGGIORNAMENTO: il fattore di riempimento predefinito è 100, da http://www.postgresql.org/docs/9.1/static/sql-createtable.html :

Il fattore di riempimento per una tabella è una percentuale compresa tra 10 e 100. 100 (impacchettamento completo) è l'impostazione predefinita


13
Un altro esempio di partizionamento
Frank Heikens

4
Nel tuo articolo 1, come fa Postgres a capire in quale delle tabelle è necessario cercare? Si seleziona dalla tabella padre e l'intervallo di date è solo un comodo esempio di divisione. La tabella padre non può conoscere questa logica. O mi sbaglio?
Alexander Palamarchuk,

4
L'esecuzione di una query sulla tabella padre è effettivamente uguale all'esecuzione di una query su UNION ALL su ogni tabella discendente su righe comuni. Il pianificatore di query è a conoscenza dei vincoli di controllo che definiscono ogni partizione e, a condizione che non si sovrappongano alle partizioni, li utilizza per determinare che può saltare il controllo delle tabelle per le quali CHECK indicano che non verrà restituita alcuna riga. Postgres documenta questo
zxq9

@avesus heh ... Il codice sopra preso da solo è degno di tanto sarcasmo. È tipico avvolgere questo genere di cose in una routine di manutenzione di qualche tipo. Questo può essere semplice come una procedura memorizzata che si prende cura di esso in alcune condizioni, un cron job o altro. È comune partizionare per data, ma di tanto in tanto mi sono ritrovato a partizionare in base all'allocazione dello spazio tabella e ciò richiede alcune informazioni esterne: i 30 minuti necessari per scrivere una partizione babysitter valgono la pena per il controllo ti dà.
zxq9

Hmm. Sei sicuro che non si blocchi? Ho una configurazione simile, ma quando eseguo il comando CLUSTER su una singola partizione, un'istruzione SELECT sui dati contenuti in un'altra partizione blocca!
E. van Putten

37

"Eredità della tabella" significa qualcosa di diverso da "eredità della classe" e servono a scopi diversi.

Postgres è incentrato sulle definizioni dei dati. A volte definizioni di dati davvero complesse. OOP (nel senso comune delle cose color Java) riguarda la subordinazione dei comportamenti alle definizioni dei dati in una singola struttura atomica. Lo scopo e il significato della parola "eredità" qui sono significativamente diversi.

In terra OOP potrei definire (essendo molto sciolto con la sintassi e la semantica qui):

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)

Le tabelle per questo potrebbero essere simili a:

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

Ma dove sono i comportamenti? Non si adattano da nessuna parte. Questo non è lo scopo degli "oggetti" così come vengono discussi nel mondo dei database, perché i database riguardano i dati, non il codice procedurale. Potresti scrivere funzioni nel database per fare calcoli per te (spesso un'idea molto buona, ma non proprio qualcosa che si adatta a questo caso) ma le funzioni non sono la stessa cosa dei metodi - metodi intesi nella forma di OOP di cui stai parlando circa sono volutamente meno flessibili.

C'è ancora una cosa da sottolineare sull'ereditarietà come dispositivo schematico: a partire da Postgres 9.2 non c'è modo di fare riferimento a un vincolo di chiave esterna su tutte le partizioni / membri della famiglia di tabelle contemporaneamente. Puoi scrivere controlli per farlo o aggirarlo in un altro modo, ma non è una funzionalità incorporata (si tratta di problemi con un'indicizzazione complessa, davvero, e nessuno ha scritto i bit necessari per renderlo automatico). Invece di utilizzare l'ereditarietà delle tabelle per questo scopo, spesso una migliore corrispondenza nel database per l'ereditarietà degli oggetti consiste nel creare estensioni schematiche alle tabelle. Qualcosa come questo:

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

Ora abbiamo un riferimento canonico per l'istanza dell'animale che possiamo usare in modo affidabile come riferimento di chiave esterna, e abbiamo una colonna "ilk" che fa riferimento a una tabella di definizioni xxx_ilk che punta alla "prossima" tabella di dati estesi ( o indica che non ce n'è se il tipo è il tipo generico stesso). Scrivere funzioni di tabella, viste, ecc. Su questo tipo di schema è così facile che la maggior parte dei framework ORM fa esattamente questo genere di cose in background quando si ricorre all'ereditarietà delle classi in stile OOP per creare famiglie di tipi di oggetti.


E se aggiungessi tutti i mamal conosciuti? Erediteresti da mammifero o avresti una chiave straniera come hai fatto qui? Il problema che ho con le chiavi esterne è che finisci per dover fare così tanti join.
puk

2
@puk Dovresti prima decidere perché stai aggiungendo tutti i mammiferi conosciuti. La forma dei dati sarà determinata dal modo in cui i dati verranno utilizzati (probabilmente non è necessario avere una tabella per animale in questo caso - considera i database per i bestiari di gioco in cui hai davvero ogni tipo di mob ). Nel caso sopra, normalmente aggiungerei una vista che è il caso più comune di mammal JOIN human, solo perché scrivere un join ogni volta è fastidioso. Ma non evitare le join . I join sono ciò che mette la R in RDBMS. Se non ti piacciono i join dovresti usare un diverso tipo di db.
zxq9

@ zxq9: Immagino che i join massicci e inefficienti a causa di tabelle di grandi dimensioni siano il punto in cui entrano in gioco le visualizzazioni materializzate? (Non uso Postgres da così tanto tempo)
Mark K Cowan

2
@MarkKCowan Joins non sono inefficienti. Ciò che è inefficiente è provare a unire campi non indicizzati e non univoci (perché lo schema non è neanche lontanamente normalizzato) a causa del design sciatto. In questi casi può essere utile una visione materializzata. Le viste materializzate sono utili anche nel caso in cui siano necessari dati normalizzati come base schematica (spesso vera), ma richiedono anche diverse rappresentazioni funzionanti e denormalizzate con cui è più facile lavorare sia per l'efficienza di elaborazione (caricamento frontale del calcolo) che per l'efficienza cognitiva. Se scrivi più che leggi, è una pessimazione, però.
zxq9

2
@MarkKCowan "Slow" è un termine relativo. Nei sistemi aziendali di grandi dimensioni e nei server di gioco in cui possiamo accettare ~ 50 ms per restituire una query, 20 join di tabella non sono mai stati un problema (in Postgres 8+, comunque) nella mia esperienza. Ma nei casi in cui la direzione vuole <1 ms di risposte a> 10 miliardi di righe si unisce a più di 5 tabelle su dati non indicizzati (o valori derivati!) ... nessun sistema al mondo si sentirà "veloce" se non fare questo join il mese scorso e metterlo da parte in un negozio K / V veloce (che è essenzialmente ciò che una visione materializzata può agire in circostanze speciali). Non si può sfuggire a un compromesso né in scrittura né in lettura.
zxq9

6

L'ereditarietà può essere utilizzata in un paradigma OOP a condizione che non sia necessario creare chiavi esterne nella tabella padre. Ad esempio, se hai un veicolo di classe astratta memorizzato in una tabella veicoli e un'auto da tavolo che eredita da essa, tutte le auto saranno visibili nella tabella veicoli ma una chiave esterna da una tabella conducenti sulla tabella veicoli non corrisponderà a queste record.

L'ereditarietà può essere utilizzata anche come strumento di partizionamento . Ciò è particolarmente utile quando si hanno tabelle destinate a crescere per sempre (tabelle di registro, ecc.).


1
I vincoli di tabella non vengono ereditati, quindi sono più che semplici chiavi esterne. È possibile applicare i vincoli di tabella alle tabelle figlie quando vengono create nel DDL oppure è possibile scrivere trigger per applicare gli stessi vincoli.
Wexxor

3

L'uso principale dell'ereditarietà è per il partizionamento, ma a volte è utile in altre situazioni. Nel mio database ci sono molte tabelle che differiscono solo per una chiave esterna. La mia "immagine" tabella "classe astratta" contiene un "ID" (la chiave primaria deve essere in ogni tabella) e un raster PostGIS 2.0. Le tabelle ereditate come "site_map" o "artifact_drawing" hanno una colonna di chiave esterna (colonna di testo "site_name" per "site_map", colonna intera "artifact_id" per la tabella "artifact_drawing" ecc.) E vincoli di chiave primaria ed esterna; il resto viene ereditato dalla tabella "immagine". Sospetto di dover aggiungere una colonna "descrizione" a tutte le tabelle di immagini in futuro, quindi questo potrebbe farmi risparmiare parecchio lavoro senza creare problemi reali (beh,

EDIT: un altro buon uso: con la gestione di due tabelle di utenti non registrati , altri RDBMS hanno problemi con la gestione delle due tabelle, ma in PostgreSQL è facile - basta aggiungere ONLYquando non si è interrotti dai dati nella tabella ereditata "utente non registrato".


2

L'unica esperienza che ho con le tabelle ereditate è il partizionamento. Funziona bene ma non è la parte più sofisticata e facile da usare di PostgreSQL.

La scorsa settimana stavamo cercando lo stesso problema OOP, ma abbiamo avuto troppi problemi con Hibernate (la nostra configurazione non piaceva), quindi non abbiamo usato l'ereditarietà in PostgreSQL.


0

Uso l'ereditarietà quando ho più di 1 su 1 relazioni tra le tabelle.

Esempio: supponiamo di voler memorizzare le posizioni della mappa degli oggetti con attributi x, y, rotazione, scala.

Supponiamo ora di avere diversi tipi di oggetti da visualizzare sulla mappa e ogni oggetto ha i propri parametri di posizione sulla mappa e i parametri della mappa non vengono mai riutilizzati.

In questi casi, l'ereditarietà delle tabelle sarebbe molto utile per evitare di dover mantenere tabelle non normalizzate o di dover creare ID di posizione e riferimenti incrociati ad altre tabelle.


-4

Usalo il meno possibile. E questo di solito significa mai, si riduce a un modo di creare strutture che violano il modello relazionale, ad esempio rompendo il principio dell'informazione e creando borse invece di relazioni.

Utilizzare invece il partizionamento delle tabelle combinato con una modellazione relazionale adeguata, comprese ulteriori forme normali.


4
Non è vero che la funzionalità di ereditarietà di PostgreSQL viola il modello relazionale infrangendo il principio dell'informazione. Il principio dell'informazione dice che tutti i dati in un database relazionale sono rappresentati da valori di dati nelle relazioni e tutti i risultati delle query sono ancora rappresentati come una relazione. ( En.wikipedia.org/wiki/Relational_model ) Questo è sempre il caso, poiché tutte le tabelle , che ereditano un'altra tabella, sono di nuovo semplici tabelle. Per questo motivo non esiste nemmeno una cosa come una "borsa", qualunque cosa significhi.
Roland

2
Ebbene, Wikipedia non è certo un riferimento per quanto riguarda il modello relazionale; si rifiuta di riconoscere che SQL viola il modello relazionale. Una borsa è un tavolo senza chiave, perché potenzialmente ha dei duplicati, non essendo quindi una relazione; una relazione deve essere un insieme.
Leandro

Questo non è un problema della funzionalità in sé, ma di come viene utilizzata. Se lavori con gli uuid come identificatori, avrai chiavi univoche su tutte le sotto-tabelle.
Roland

Hai ragione, ma il problema qui è che l'ereditarietà porta il modellatore a ignorare il modello relazionale. Gli UUID non sono chiavi reali, ma surrogate. Bisogna ancora dichiarare le chiavi naturali.
Leandro
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.