Capitolo 01
Che cosa fa un DBMS al posto tuo
Future users of large data banks must be protected from having to know how the data is organized in the machine (the internal representation).
La lezione precedente si è chiusa con una scelta fra due difetti. Con le copie dei dati, i file finiscono per contraddirsi. Senza queste copie, ogni domanda che poniamo ai dati richiede un incrocio con del codice scritto a mano, e nessun file controlla che il nome scritto in un’assegnazione compaia realmente fra gli studenti. La prima lezione del capitolo aveva lasciato in sospeso anche un’altra domanda: che cosa succede se due professori aggiungono un’assegnazione al registro nello stesso momento? In questa lezione mettiamo il registro in un database e guardiamo, un problema alla volta, che cosa fa il DBMS al posto del tuo programma.
Il registro in un database
In questa lezione il registro dei punti passa dai file JSON a un database, e a gestirlo è un DBMS.
Con un DBMS il tuo programma non apre più un file e non lo scorre: manda una richiesta al DBMS e riceve la risposta.
Ripartiamo dalla regola con cui si è chiusa la lezione precedente: ogni informazione scritta una volta sola, nel posto che le appartiene. In un database quei posti sono le tabelle, e il registro dei punti ne occupa tre. La tabella studente contiene nome, casa e anno, come studenti.json. La tabella professore è l’elenco dei professori, che nell’archivio JSON non c’era. La tabella assegnazione registra chi ha ricevuto i punti, quanti e da chi, e non contiene più la casa. Il blocco qui sotto mostra le prime cinque righe della tabella assegnazione:
In una tabella ogni riga descrive un elemento e ogni colonna un’informazione, la stessa per tutte le righe: nella tabella assegnazione ogni riga è un’assegnazione. La colonna id è un numero progressivo che il database dà a ogni riga. Le tabelle si collegano attraverso i nomi, come i file della lezione precedente: il valore nella colonna studente indica una riga della tabella studente, quello nella colonna professore una riga della tabella professore. Il collegamento per nome regge finché due studenti non hanno lo stesso nome; il problema, e il modo di risolverlo, arrivano nel prossimo capitolo.
Ogni assegnazione indica per nome una riga di studente e una di professore: qui Ron Weasley e McGonagall.
Domande senza programma
Nella prima lezione ogni domanda era un programma da scrivere. Al DBMS invece si fa una richiesta, e lui risponde con una tabella. La richiesta è scritta in un linguaggio ben strutturato apposito, e si chiama query. Il blocco qui sotto chiede il totale dei punti di ogni casa; la query per ora è nascosta perché apprenderemo la sintassi nei prossimi capitoli, ma se sei curioso con il pulsante “Mostra la query” puoi leggerla.
Grifondoro ha tre punti, come nel conteggio di McGonagall della prima lezione. Questa volta però la casa non sta nella tabella assegnazione: per sommare i punti di ogni casa il DBMS ha incrociato ogni assegnazione con la riga dello studente. È lo stesso tipo di incrocio che nella prima lezione richiedeva il dizionario anno_di, e che nella lezione precedente era il prezzo della regola “un fatto, un posto”. Adesso quel prezzo lo paga il DBMS.
Per vedere che cosa cambia nel codice, mettiamo a confronto la domanda di Silente della prima lezione, i punti raccolti dagli studenti di ogni anno, scritta nei due modi.
- Con i file
- Con il DBMS
anno_di = {}
for studente in studenti:
anno_di[studente["nome"]] = studente["anno"]
per_anno = {}
for assegnazione in registro:
anno = anno_di[assegnazione["studente"]]
per_anno[anno] = per_anno.get(anno, 0) + assegnazione["punti"]
SELECT studente.anno, SUM(assegnazione.punti) AS punti
FROM assegnazione
JOIN studente ON studente.nome = assegnazione.studente
GROUP BY studente.anno;Il programma Python descrive i passi: costruisci un dizionario, scorri il registro, per ogni assegnazione cerca l’anno e accumula i punti. La query descrive il risultato, e si legge quasi come una frase: l’anno dello studente e la somma dei punti, dalle assegnazioni unite agli studenti con lo stesso nome, raggruppate per anno. Non dice in che ordine leggere le righe né quale struttura usare per accumulare: lo sceglie il DBMS. Con una query il DBMS funziona come la Stanza delle Necessità: chiedi quello che ti serve, e come prepararlo è affar suo. Esegui la query nella seconda scheda: per il primo anno dà 23 punti e per il terzo −10, gli stessi numeri del programma della prima lezione.
Un linguaggio in cui scrivi che cosa vuoi ottenere, e non come, si chiama dichiarativo; Python, in cui scrivi i passi, è un linguaggio imperativo. Il linguaggio delle query è SQL, e lo impareremo a leggere e a scrivere a partire dal capitolo sulle interrogazioni: per adesso basta riconoscerne la forma. Separare la domanda dal modo di eseguirla è l’idea con cui Codd apriva nel 1970 l’articolo citato in cima a questa pagina: chi interroga un archivio non deve sapere come i dati sono disposti nella macchina.
Anche la domanda di McGonagall che nella prima lezione hai risolto modificando un programma, quale professore ha tolto più punti a Grifondoro, diventa una query:
Per il DBMS non esistono domande previste e domande nuove: ogni domanda è una query, e il modo di eseguirla lo sceglie lui.
Il nome che non esiste
Nella prima lezione abbiamo aggiunto al registro un’assegnazione di Neville Logbottom, con il nome scritto male (manca una “n”). Nel file JSON l’assegnazione è entrata senza nessuna segnalazione, e l’errore è arrivato dopo, come KeyError, al programma che calcolava i punti per anno. Il blocco qui sotto ripete l’errore nel database: aggiunge alla tabella assegnazione una riga con lo stesso nome sbagliato. È un’istruzione di scrittura, e si legge come la query di prima: inserisci nella tabella assegnazione, nelle colonne studente, punti e professore, questi valori. Prima di eseguire il blocco, prova a prevedere che cosa fa il DBMS.
INSERT INTO assegnazione (studente, punti, professore)
VALUES ('Neville Logbottom', 5, 'Sprout');
Chi ha creato la tabella assegnazione ha dichiarato la colonna studente come riferimento alla tabella studente: ogni valore della colonna deve essere il nome di una riga che esiste. Una regola sui dati che il DBMS fa rispettare si chiama vincolo. Il vincolo che lega una colonna alle righe di un’altra tabella si chiama vincolo di chiave esterna, e lo studieremo quando parleremo di integrità referenziale.
Con i file, l’errore arrivava a chi leggeva: al programma dei punti per anno, magari mesi dopo, e solo se quel programma usava il nome. Un programma che saltava i nomi sconosciuti perdeva i cinque punti senza dire niente. Con il DBMS l’errore arriva a chi scrive, nel momento in cui scrive. Il controllo sta nel database e non in un programma, quindi vale per il programma di McGonagall e per quello che qualcuno scriverà l’anno prossimo, anche se chi lo scrive non pensa a controllare i nomi.
Adesso prova a cambiare nel blocco il nome in 'Harry Potter' e riesegui.
Il vincolo ha anche un effetto sull’ordine delle scritture. Nella lezione precedente Lupin non poteva entrare nell’archivio finché non assegnava punti a qualcuno. Nel database la sua prima assegnazione, i dieci punti a Neville, viene rifiutata: anche la colonna professore della tabella assegnazione ha un vincolo verso la tabella professore, e Lupin non c’è. Esegui il blocco qui sotto e otterrai lo stesso errore di prima. Poi fai in modo che il database accetti l’assegnazione, senza cambiarla: l’INSERT di Neville Longbottom ti fa da modello.
INSERT INTO assegnazione (studente, punti, professore)
VALUES ('Neville Longbottom', 10, 'Lupin');Due professori nello stesso momento
Resta la domanda della prima lezione. Per aggiungere un’assegnazione a punti.json un programma rilegge il file, aggiunge la riga alla lista e riscrive tutto. Supponiamo che McGonagall e Piton aggiungano un’assegnazione quasi nello stesso momento, ognuno dal proprio computer, sullo stesso file condiviso. Il blocco qui sotto ricostruisce la sequenza in un ordine fissato: la variabile disco fa la parte del file, e ogni professore ha in memoria la propria copia del registro. Il file parte da 18 assegnazioni. Prima di eseguire il blocco, prova a prevedere quante assegnazioni contiene il file alla fine.
La figura qui sotto ricostruisce i due salvataggi.
McGonagall salva la sua copia: il file ha 19 assegnazioni. Prova a prevedere, poi premi «Avanti».
- Sul disco, punti.json contiene 18 assegnazioni.
- McGonagall e Piton leggono il file: ognuno ha in memoria la propria copia.
- Ognuno aggiunge la propria assegnazione alla propria copia: Ron −5, Draco +5.
- McGonagall salva la sua copia: il file ha 19 assegnazioni.
- Piton salva la sua copia sopra il file: l'assegnazione di Ron sparisce.
- Nel file restano 19 assegnazioni, non 20: l'aggiornamento di McGonagall è perso.
Nessuna istruzione ha dato errore, e ognuna ha fatto esattamente quello che diceva. McGonagall crede di aver tolto cinque punti a Ron, e nel file quei punti non ci sono. Ron non se ne lamenterà. Un errore come questo si chiama aggiornamento perso (in inglese lost update): due programmi leggono lo stesso dato, lo modificano ciascuno per conto suo, e l’ultimo che scrive cancella il lavoro dell’altro. Il blocco ha fissato l’ordine delle operazioni. Nella realtà l’ordine dipende da chi salva per ultimo e da quanto tempo passa fra lettura e scrittura: a volte l’errore succede e a volte no, e un errore che succede solo a volte è il più difficile da trovare.
Con un database nessun professore riscrive il registro. Il programma di McGonagall manda al DBMS un INSERT con la riga di Ron, quello di Piton un altro INSERT con la riga di Draco. Le richieste arrivano tutte al DBMS, che coordina chi scrive: ogni INSERT aggiunge la propria riga e non riscrive le altre, quindi alla fine le righe sono 20. Per lo stesso motivo, aggiungere un’assegnazione non richiede più di riscrivere tutto il registro, come succedeva con json.dump.
La riga di Ron è ancora nella tabella: 20 righe, nessuna assegnazione persa.
- Nel database, la tabella assegnazione contiene 18 righe.
- McGonagall e Piton hanno in memoria solo la propria assegnazione: Ron −5, Draco +5.
- McGonagall manda un INSERT: il DBMS aggiunge la riga di Ron, e le righe diventano 19.
- Piton manda un INSERT: il DBMS aggiunge la riga di Draco, e le righe diventano 20.
- La riga di Ron è ancora nella tabella: 20 righe, nessuna assegnazione persa.
Il coordinamento fra due utenti non si può mostrare in questa pagina, perché il database del browser ha un solo utente, cioè tu. Torneremo sul problema nel capitolo sulla modifica dei dati, dove il caso si fa più difficile, con due professori che non aggiungono righe nuove ma correggono la stessa.
Nel database della lezione, la professoressa Sprout scrive per sbaglio un’assegnazione di cinque punti a 'Hermione Granger', che di punti non ha certo bisogno, invece che a Neville. Che cosa fa il DBMS?
Perché, con un database, le assegnazioni di McGonagall e Piton non si perdono?
Un compagno sostiene che un database è un file JSON più grande e più veloce. Quale fatto visto nella lezione lo smentisce?