Indici e prestazioni di base: aiutare PostgreSQL a trovare i dati più velocemente

Bentornato.
Nella lezione precedente hai imparato le funzioni aggregate.
Hai imparato a contare righe.
Sommare valori.
Calcolare medie.
Trovare valori minimi e massimi.
Raggruppare dati.
Filtrare gruppi con HAVING.
Molto bene.
Ora PostgreSQL può rispondere a domande come:
Quanti prodotti abbiamo?
Qual è il valore totale del magazzino?
Quanti ordini ha ogni cliente?
Quale categoria ha più prodotti?
Ottimo.
Ma ora arriva un’altra domanda.
Una domanda molto seria.
Cosa succede quando la tabella diventa enorme?
Quando una tabella ha dieci righe, tutto è veloce.
Quando una tabella ha dieci milioni di righe, PostgreSQL inizia a guardarti in modo diverso.
Tipo:
Sei sicuro di questa query?
Qui entrano in gioco gli indici.
Gli indici aiutano PostgreSQL a trovare i dati più velocemente.
Non sono magia.
Ma sono potenti.
Come una buona mappa.
Senza un indice, PostgreSQL potrebbe dover leggere tutta la tabella.
Con un indice, PostgreSQL può arrivare più velocemente ai dati che gli servono.
Molto utile.
Molto database.
Molto “per favore non far piangere il mio server”.
Cosa imparerai
In questa lezione imparerai:
- perché le query possono diventare lente;
- cos’è un indice;
- come gli indici aiutano PostgreSQL a trovare righe più velocemente;
- cos’è una scansione sequenziale;
- cos’è una scansione tramite indice;
- come usare
EXPLAIN; - come usare
EXPLAIN ANALYZE; - come creare un indice;
- quando gli indici sono utili;
- quando gli indici non sono utili;
- perché gli indici non sono gratis;
- come gli indici influenzano inserimenti e aggiornamenti;
- come funzionano gli indici unici;
- come funzionano gli indici su più colonne;
- errori comuni con gli indici.
Alla fine di questa lezione capirai l’idea base delle prestazioni nei database.
Non fisica nucleare.
Non ancora.
Solo quanto basta per smettere di scrivere query che fanno sembrare PostgreSQL stanco.
Un obiettivo nobile.
Preparare il database
Apri PostgreSQL:
sudo -iu postgres psql
Collegati al tuo database:
\c learning_postgresql
Se il database non esiste, crealo:
CREATE DATABASE learning_postgresql;
Poi collegati:
\c learning_postgresql
Ora elimina la vecchia tabella, se esiste:
DROP TABLE IF EXISTS products;
Crea una nuova tabella products:
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
category VARCHAR(100) NOT NULL,
price NUMERIC(10, 2) CHECK (price >= 0),
quantity INTEGER CHECK (quantity >= 0),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Questa tabella è semplice.
Ma è sufficiente per imparare gli indici.
Abbiamo:
- nome del prodotto;
- categoria;
- prezzo;
- quantità;
- data di creazione.
Una normale tabella piccola.
Per ora.
Tra poco diventerà un piccolo esercito di righe.
PostgreSQL è pronto.
Forse.
Inserire alcuni dati
Inserisci alcuni prodotti:
INSERT INTO products (name, category, price, quantity)
VALUES
('Laptop', 'Electronics', 900.00, 5),
('Mouse', 'Electronics', 25.00, 30),
('Keyboard', 'Electronics', 70.00, 15),
('Desk Chair', 'Furniture', 150.00, 8),
('Bookshelf', 'Furniture', 120.00, 4),
('Notebook', 'Office', 5.00, 100),
('Pen', 'Office', 2.00, 200),
('Whiteboard', 'Office', 80.00, 3);
Controlla i dati:
SELECT * FROM products;
Questa tabella è minuscola.
Una tabella minuscola è veloce anche senza indici.
PostgreSQL può leggerla quasi sbattendo le palpebre.
Ma le applicazioni reali non restano piccole per sempre.
I dati crescono.
I log crescono.
Gli ordini crescono.
Gli utenti crescono.
Il database cresce.
Poi un giorno una query diventa lenta e tutti guardano lo sviluppatore.
Molto ingiusto.
Ma a volte anche corretto.
Aggiungere più righe per i test
Per vedere le idee sulle prestazioni, ci servono più righe.
PostgreSQL ha una funzione utile chiamata generate_series.
Può generare molte righe.
Esegui questo:
INSERT INTO products (name, category, price, quantity)
SELECT
'Product ' || number,
CASE
WHEN number % 4 = 0 THEN 'Electronics'
WHEN number % 4 = 1 THEN 'Furniture'
WHEN number % 4 = 2 THEN 'Office'
ELSE 'Kitchen'
END,
(number % 100) + 1,
(number % 50) + 1
FROM generate_series(1, 100000) AS number;
Questo inserisce centomila prodotti.
Non prodotti reali.
Prodotti finti.
Ma prodotti finti utili.
Come pesi da allenamento per PostgreSQL.
Ora controlla il numero di righe:
SELECT COUNT(*) AS total_products
FROM products;
Dovresti vedere più di centomila righe.
Ora la tabella è abbastanza grande per rendere gli esempi di prestazioni più interessanti.
Non enorme.
Ma non più un giocattolino.
Perché le query diventano lente
Una query può essere lenta per molti motivi.
Per un principiante, il motivo più comune è semplice:
PostgreSQL deve controllare troppe righe.
Esempio:
SELECT *
FROM products
WHERE category = 'Electronics';
Se non c’è un indice su category, PostgreSQL potrebbe dover controllare ogni riga.
Una per una.
Come cercare un foglio in un garage disordinato.
Possibile.
Ma non elegante.
Questa si chiama scansione sequenziale.
Scansione sequenziale
Una scansione sequenziale significa che PostgreSQL legge la tabella riga per riga.
Controlla ogni riga e si chiede:
Questa riga corrisponde alla condizione?
Per tabelle piccole va benissimo.
Per tabelle grandi può diventare lento.
Esempio:
SELECT *
FROM products
WHERE category = 'Electronics';
Senza un indice su category, PostgreSQL potrebbe leggere tutta la tabella.
Questo non è sempre sbagliato.
A volte una scansione sequenziale è la scelta migliore.
Ma se cerchi spesso usando una colonna, un indice può aiutare.
La parola importante è spesso.
Non creare indici a caso.
Gli indici sono strumenti.
Non decorazioni.
Cos’è un indice?
Un indice è una struttura speciale del database che aiuta PostgreSQL a trovare righe più velocemente.
Pensa a un libro.
Se un libro non ha indice, devi sfogliare pagina dopo pagina.
Se un libro ha un indice, puoi cercare un argomento e saltare alla pagina giusta.
Un indice di database funziona con la stessa idea.
Senza indice:
Leggi molte righe e controllale.
Con indice:
Usa l’indice per trovare più velocemente le righe corrispondenti.
Gli indici in PostgreSQL di solito sono salvati separatamente dalla tabella.
La tabella contiene i dati reali.
L’indice contiene puntatori organizzati a quei dati.
Molto semplificato.
Ma per ora va benissimo.
Come un GPS per le righe.
Senza voce fastidiosa.
Controllare una query con EXPLAIN
PostgreSQL può mostrare come pensa di eseguire una query.
Usa EXPLAIN.
EXPLAIN
SELECT *
FROM products
WHERE category = 'Electronics';
Potresti vedere qualcosa del genere:
Seq Scan on products
Filter: ((category)::text = 'Electronics'::text)
L’output esatto può essere diverso.
Ma la parte importante è:
Seq Scan
Questo significa che PostgreSQL prevede di leggere la tabella.
Leggerà le righe e le filtrerà.
È un’informazione molto utile.
PostgreSQL ti sta mostrando il suo piano.
Come un GPS del database.
Ma con meno colori.
EXPLAIN ANALYZE
EXPLAIN mostra il piano.
EXPLAIN ANALYZE esegue davvero la query e mostra cosa è successo.
Esegui:
EXPLAIN ANALYZE
SELECT *
FROM products
WHERE category = 'Electronics';
Potresti vedere:
Seq Scan on products
Filter: ((category)::text = 'Electronics'::text)
Rows Removed by Filter: ...
Planning Time: ...
Execution Time: ...
I numeri esatti dipendono dal tuo computer.
Le parti importanti sono:
Seq Scan
Execution Time
Rows Removed by Filter
Rows Removed by Filter significa che PostgreSQL ha controllato righe e ne ha scartate molte.
Questo è lavoro.
A volte lavoro necessario.
A volte lavoro evitabile.
EXPLAIN ANALYZE è molto utile.
Ma ricordati:
Esegue davvero la query.
Quindi fai attenzione con le query che modificano dati.
Non usarlo con leggerezza su un DELETE pericoloso.
PostgreSQL non ti chiederà:
Sei emotivamente pronto?
Eseguirà e basta.
Creare un indice
Ora crea un indice su category:
CREATE INDEX idx_products_category
ON products(category);
Il nome idx_products_category è solo un nome.
Uno stile comune è:
idx_tabella_colonna
Ora esegui di nuovo la query:
EXPLAIN ANALYZE
SELECT *
FROM products
WHERE category = 'Electronics';
Potresti vedere che viene usato un indice.
Per esempio:
Bitmap Index Scan
Bitmap Heap Scan
oppure:
Index Scan
Il piano esatto dipende da PostgreSQL e dai tuoi dati.
Il punto importante è questo:
PostgreSQL ora ha un’altra possibilità.
Può usare l’indice.
Senza indice, doveva leggere la tabella.
Con indice, può trovare le righe più velocemente.
Può.
Non sempre.
PostgreSQL sceglie ciò che ritiene migliore.
È intelligente.
Di solito.
Perché PostgreSQL può usare ancora una scansione sequenziale
A volte crei un indice e PostgreSQL usa comunque una scansione sequenziale.
Non andare nel panico.
Può essere normale.
Perché?
Perché se molte righe corrispondono alla condizione, leggere tutta la tabella può essere più veloce.
Esempio:
SELECT *
FROM products
WHERE category = 'Electronics';
Se il venticinque percento della tabella è Electronics, PostgreSQL può decidere:
Corrispondono molte righe. Leggo la tabella.
Un indice è più utile quando la query restituisce una piccola parte della tabella.
Per esempio:
SELECT *
FROM products
WHERE id = 50000;
Questo è molto selettivo.
Una sola riga.
Perfetto per un indice.
Cercare un prodotto per id è veloce perché id è già indicizzato dalla chiave primaria.
Le chiavi primarie creano automaticamente indici.
PostgreSQL lo ha fatto per te.
Molto gentile.
Molto professionale.
Indice della chiave primaria
Quando crei questo:
id SERIAL PRIMARY KEY
PostgreSQL crea automaticamente un indice unico per id.
Ecco perché questa query è veloce:
SELECT *
FROM products
WHERE id = 50000;
Controlla:
EXPLAIN ANALYZE
SELECT *
FROM products
WHERE id = 50000;
Dovresti vedere qualcosa come:
Index Scan using products_pkey
products_pkey è l’indice della chiave primaria.
Non lo hai creato manualmente.
PostgreSQL lo ha creato perché le chiavi primarie devono essere uniche e ricercabili.
Questo è uno dei motivi per cui le chiavi primarie sono importanti.
Non sono decorazioni.
Sono carte d’identità del database.
Indice su price
Creiamo un indice su price.
CREATE INDEX idx_products_price
ON products(price);
Ora esegui:
EXPLAIN ANALYZE
SELECT *
FROM products
WHERE price = 50.00;
PostgreSQL potrebbe usare l’indice.
Dipende dai dati.
Ora prova:
EXPLAIN ANALYZE
SELECT *
FROM products
WHERE price > 90.00;
Un indice può aiutare anche con ricerche per intervallo.
Esempi:
WHERE price = 50.00
WHERE price > 90.00
WHERE price BETWEEN 10.00 AND 20.00
Gli indici sono particolarmente utili quando cerchi, filtri, ordini o fai join usando una colonna.
Ma di nuovo:
Non creare indici su ogni colonna.
Quella non è ottimizzazione.
È coriandoli da database.
E i coriandoli sono fastidiosi da pulire.
Indice e ORDER BY
Gli indici possono aiutare anche con l’ordinamento.
Esempio:
EXPLAIN ANALYZE
SELECT *
FROM products
ORDER BY price ASC
LIMIT 10;
Dato che abbiamo creato un indice su price, PostgreSQL potrebbe usarlo per trovare più velocemente i prodotti più economici.
Questo è utile per query come:
Mostra i prodotti più economici.
Mostra i prodotti più costosi.
Mostra i post più recenti.
Mostra gli ultimi ordini.
Un esempio comune è un indice su created_at.
CREATE INDEX idx_products_created_at
ON products(created_at);
Poi questa query potrebbe diventare più veloce:
SELECT *
FROM products
ORDER BY created_at DESC
LIMIT 10;
Questo è molto comune nelle applicazioni reali.
Blog.
Ordini.
Messaggi.
Log.
Tutto vuole le ultime dieci cose.
Perché a quanto pare gli esseri umani amano “l’ultimo”.
Indice e JOIN
Gli indici possono aiutare anche con i JOIN.
Immagina due tabelle:
orders
customers
Di solito:
orders.customer_id references customers.id
Quando fai un join:
SELECT
o.id,
c.name,
o.total
FROM orders AS o
JOIN customers AS c
ON o.customer_id = c.id;
PostgreSQL deve abbinare le righe.
Gli indici sulle colonne usate nei join possono aiutare.
Le chiavi primarie sono indicizzate automaticamente.
Quindi customers.id è già indicizzato se è una chiave primaria.
Ma le chiavi esterne non sono sempre indicizzate automaticamente.
Questo significa che orders.customer_id potrebbe aver bisogno di un indice se fai spesso join o filtri con quella colonna.
Esempio:
CREATE INDEX idx_orders_customer_id
ON orders(customer_id);
Questo è un indice molto comune nei progetti reali.
Le colonne con chiavi esterne sono spesso buone candidate per un indice.
Non sempre.
Ma spesso.
Risposta numero uno dei database:
Dipende.
Fastidioso.
Ma vero.
Indice unico
Un indice unico impedisce valori duplicati.
Esempio:
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL,
name VARCHAR(100) NOT NULL
);
Crea un indice unico:
CREATE UNIQUE INDEX idx_users_email_unique
ON users(email);
Inserisci un utente:
INSERT INTO users (email, name)
VALUES ('anna@example.com', 'Anna');
Prova a inserire un altro utente con la stessa email:
INSERT INTO users (email, name)
VALUES ('anna@example.com', 'Another Anna');
PostgreSQL lo rifiuterà.
Bene.
Le email di solito devono essere uniche.
Un indice unico fa due cose:
- aiuta a cercare più velocemente;
- protegge i dati dai duplicati.
Molto utile.
Molto responsabile.
Come una guardia di sicurezza per la tua tabella.
Ma con meno occhiali da sole.
Vincolo UNIQUE o indice unico?
Puoi anche scrivere:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL
);
Questo crea un vincolo unico.
PostgreSQL crea anche un indice unico dietro le quinte.
Per un principiante, usa UNIQUE nella definizione della tabella quando fa parte della regola dei dati.
Esempio:
L’email deve essere unica.
Lo username deve essere unico.
Il codice prodotto deve essere unico.
Usa indici espliciti quando vuoi migliorare le prestazioni di ricerca.
Regola semplice:
Regola dei dati: vincolo.
Aiuto per le prestazioni: indice.
Non è perfetta.
Ma è molto pratica.
Indici su più colonne
Un indice può includere più di una colonna.
Esempio:
CREATE INDEX idx_products_category_price
ON products(category, price);
Questo può aiutare query come:
SELECT *
FROM products
WHERE category = 'Electronics'
ORDER BY price ASC;
L’indice è ordinato prima per category, poi per price.
L’ordine delle colonne conta.
Questo indice è utile per:
WHERE category = 'Electronics'
e:
WHERE category = 'Electronics'
ORDER BY price ASC
Ma può essere meno utile per:
WHERE price = 50.00
Perché?
Perché price è la seconda colonna dell’indice.
La prima colonna è category.
Gli indici su più colonne sono potenti.
Ma devi crearli in base alle query reali.
Non in base alle vibrazioni.
I database non ottimizzano le vibrazioni.
Purtroppo.
Gli indici non sono gratis
Gli indici rendono alcune letture più veloci.
Ma hanno costi.
Un indice:
- usa spazio su disco;
- deve essere aggiornato quando i dati cambiano;
- può rallentare
INSERT; - può rallentare
UPDATE; - può rallentare
DELETE; - aggiunge complessità.
Quando inserisci una riga, PostgreSQL deve inserire i dati nella tabella.
Ma deve anche aggiornare gli indici.
Se una tabella ha molti indici, la scrittura diventa più pesante.
Quindi non creare indici ovunque.
Una tabella con troppi indici è come una persona con dieci zaini.
Forse preparata.
Sicuramente più lenta.
Quando dovresti creare un indice?
Crea un indice quando:
- cerchi spesso per una colonna;
- filtri spesso per una colonna con
WHERE; - ordini spesso per una colonna con
ORDER BY; - fai spesso join usando una colonna;
- la tabella è abbastanza grande perché le prestazioni contino;
- la query restituisce una piccola parte della tabella;
EXPLAIN ANALYZEmostra una scansione lenta.
Esempi:
WHERE email = 'anna@example.com'
WHERE customer_id = 10
WHERE created_at >= '2026-01-01'
ORDER BY created_at DESC LIMIT 10
JOIN orders ON orders.customer_id = customers.id
Questi sono punti comuni in cui gli indici possono aiutare.
Ma non indovinare alla cieca.
Misura.
Poi crea l’indice.
Poi misura di nuovo.
Questo è il modo adulto.
Un po’ noioso.
Molto efficace.
Quando un indice può non aiutare
Un indice può non aiutare quando:
- la tabella è molto piccola;
- la query restituisce quasi tutta la tabella;
- la colonna ha pochissimi valori diversi;
- usi la colonna in un modo che impedisce l’uso dell’indice;
- l’indice non corrisponde alla query;
- i dati cambiano molto spesso e le letture sono rare.
Esempio:
WHERE category = 'Electronics'
Se esistono solo quattro categorie e ogni categoria ha molte righe, l’indice potrebbe non aiutare molto.
Altro esempio:
WHERE quantity > 0
Se quasi ogni prodotto ha quantità maggiore di zero, PostgreSQL potrebbe preferire una scansione sequenziale.
Perché quasi tutto corrisponde.
Un indice è utile quando può evitare lavoro.
Se la query ha bisogno quasi di tutto, non c’è molto lavoro da evitare.
Triste ma logico.
Le funzioni possono nascondere gli indici
Fai attenzione alle funzioni in WHERE.
Supponiamo che tu abbia un indice su email.
CREATE INDEX idx_users_email
ON users(email);
Questa query può usare l’indice:
SELECT *
FROM users
WHERE email = 'anna@example.com';
Ma questa query potrebbe non usare il normale indice:
SELECT *
FROM users
WHERE LOWER(email) = 'anna@example.com';
Perché?
Perché PostgreSQL non sta cercando il valore grezzo di email.
Sta cercando il risultato di LOWER(email).
È diverso.
Per questo tipo di query potresti aver bisogno di un indice su espressione:
CREATE INDEX idx_users_lower_email
ON users(LOWER(email));
Ora PostgreSQL può usare l’indice per:
SELECT *
FROM users
WHERE LOWER(email) = 'anna@example.com';
Questo è un po’ più avanzato.
Ma importante.
Se la query trasforma la colonna, un indice normale potrebbe non aiutare.
Il database è intelligente.
Ma non legge nel pensiero.
Per fortuna.
LIKE e indici
Gli indici a volte possono aiutare con LIKE.
Esempio:
SELECT *
FROM products
WHERE name LIKE 'Product 12%';
Questo cerca nomi che iniziano con Product 12.
In alcuni casi può usare un indice.
Ma questa query è più difficile:
SELECT *
FROM products
WHERE name LIKE '%12';
Perché?
Perché il pattern inizia con un wildcard.
PostgreSQL non può saltare facilmente all’inizio del valore.
Deve cercare di più.
Regola semplice:
LIKE 'abc%' può essere amico degli indici.
LIKE '%abc' di solito non è amico degli indici.
LIKE '%abc%' di solito non è amico degli indici.
Esistono tipi speciali di indici per la ricerca testuale avanzata.
Ma per ora ricorda questo:
Cercare dall’inizio è più facile.
Cercare dal centro è più difficile.
Come trovare il titolo di un libro quando ricordi solo una parola in mezzo.
Buona fortuna, coraggioso bibliotecario.
Eliminare un indice
A volte crei un indice e poi decidi che non è utile.
Puoi rimuoverlo.
Esempio:
DROP INDEX IF EXISTS idx_products_price;
Questo elimina l’indice.
Non elimina la tabella.
Non elimina i dati.
Solo l’indice.
Comunque, fai attenzione nei progetti reali.
Gli indici possono essere usati da query importanti.
Eliminare l’indice sbagliato può rendere tutto lento.
E poi il server comincia la sua opera drammatica.
Errori comuni
Creare indici su ogni colonna
Cattiva idea:
Ogni colonna riceve un indice!
No.
Fermati.
Questa non è un albero di Natale.
Gli indici hanno costi.
Crea indici in base alle query reali.
Non usare EXPLAIN ANALYZE
Indovinare non è lavorare sulle prestazioni.
Usa:
EXPLAIN ANALYZE
SELECT *
FROM products
WHERE category = 'Electronics';
Misura prima e dopo.
Senza misurare, stai facendo astrologia da database.
Aspettarsi che gli indici sistemino query brutte
Gli indici aiutano.
Ma non sistemano tutto.
Una query terribile può restare terribile anche con un indice.
Se selezioni troppi dati, fai troppi join male, o filtri in modo strano, gli indici potrebbero non salvarti.
Gli indici sono strumenti.
Non miracoli.
Dimenticare che gli indici rallentano le scritture
Se la tabella riceve molti inserimenti e aggiornamenti, troppi indici possono peggiorare le prestazioni.
Le letture possono diventare più veloci.
Le scritture possono diventare più lente.
C’è sempre un compromesso.
Il database dà.
Il database prende.
Molto filosofico.
Ignorare gli indici sulle chiavi esterne
Le chiavi esterne sono importanti per le relazioni.
Ma se fai spesso join o filtri usando una colonna di chiave esterna, considera di indicizzarla.
Esempio:
CREATE INDEX idx_orders_customer_id
ON orders(customer_id);
Questo è comune nelle applicazioni reali.
Non sempre necessario.
Ma molto spesso utile.
Pratica
Crea un indice su category:
CREATE INDEX idx_products_category
ON products(category);
Controlla una query:
EXPLAIN ANALYZE
SELECT *
FROM products
WHERE category = 'Electronics';
Crea un indice su price:
CREATE INDEX idx_products_price
ON products(price);
Controlla una query sul prezzo:
EXPLAIN ANALYZE
SELECT *
FROM products
WHERE price BETWEEN 10.00 AND 20.00;
Controlla l’ordinamento:
EXPLAIN ANALYZE
SELECT *
FROM products
ORDER BY price ASC
LIMIT 10;
Crea un indice su created_at:
CREATE INDEX idx_products_created_at
ON products(created_at);
Controlla gli ultimi prodotti:
EXPLAIN ANALYZE
SELECT *
FROM products
ORDER BY created_at DESC
LIMIT 10;
Crea un indice su più colonne:
CREATE INDEX idx_products_category_price
ON products(category, price);
Controlla questa query:
EXPLAIN ANALYZE
SELECT *
FROM products
WHERE category = 'Office'
ORDER BY price ASC
LIMIT 10;
Esegui queste query.
Guarda l’output.
Non preoccuparti se il piano è diverso dagli esempi.
PostgreSQL sceglie i piani in base ai tuoi dati, ai tuoi indici, alle impostazioni e al tuo computer.
L’obiettivo non è memorizzare ogni piano.
L’obiettivo è capire cosa PostgreSQL sta cercando di fare.
Per ora è abbastanza.
Mini sfida
Crea due tabelle:
customers
orders
Regole:
- un cliente può avere molti ordini;
- ogni ordine appartiene a un cliente;
- l’email del cliente deve essere unica;
- gli ordini hanno un valore totale;
- gli ordini hanno una data di creazione.
Crea le tabelle:
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS customers;
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INTEGER REFERENCES customers(id),
total NUMERIC(10, 2) CHECK (total >= 0),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Inserisci i clienti:
INSERT INTO customers (email, name)
VALUES
('anna@example.com', 'Anna'),
('marco@example.com', 'Marco'),
('sofia@example.com', 'Sofia');
Inserisci gli ordini:
INSERT INTO orders (customer_id, total)
SELECT
(number % 3) + 1,
(number % 200) + 10
FROM generate_series(1, 50000) AS number;
Ora crea indici utili:
CREATE INDEX idx_orders_customer_id
ON orders(customer_id);
CREATE INDEX idx_orders_created_at
ON orders(created_at);
CREATE INDEX idx_orders_total
ON orders(total);
Testa queste query con EXPLAIN ANALYZE:
EXPLAIN ANALYZE
SELECT *
FROM customers
WHERE email = 'anna@example.com';
EXPLAIN ANALYZE
SELECT *
FROM orders
WHERE customer_id = 1;
EXPLAIN ANALYZE
SELECT *
FROM orders
ORDER BY created_at DESC
LIMIT 10;
EXPLAIN ANALYZE
SELECT *
FROM orders
WHERE total > 150.00;
Poi chiediti:
Quali indici sono stati usati?
Quali query hanno ancora usato una scansione sequenziale?
Perché?
La query ha restituito molte righe o poche righe?
È così che inizia il vero lavoro sulle prestazioni.
Non indovinando.
Osservando.
PostgreSQL lascia indizi.
Devi solo leggerli.
Come un detective.
Ma con più punti e virgola.
Riassunto
Oggi hai imparato:
- le query possono diventare lente quando le tabelle crescono;
- una scansione sequenziale legge le righe una per una;
- un indice aiuta PostgreSQL a trovare righe più velocemente;
- le chiavi primarie creano automaticamente indici;
EXPLAINmostra il piano della query;EXPLAIN ANALYZEesegue la query e mostra dettagli reali di esecuzione;- gli indici possono aiutare con
WHERE; - gli indici possono aiutare con
ORDER BY; - gli indici possono aiutare con
JOIN; - gli indici unici impediscono valori duplicati;
- gli indici su più colonne dipendono dall’ordine delle colonne;
- gli indici usano spazio su disco;
- gli indici possono rallentare inserimenti, aggiornamenti ed eliminazioni;
- non ogni colonna ha bisogno di un indice;
- le prestazioni vanno misurate, non indovinate.
Questo è un passo molto importante.
Non stai più solo scrivendo SQL.
Stai iniziando a pensare a come PostgreSQL esegue SQL.
È un livello diverso.
Un livello migliore.
Un livello in cui il database fa ancora un po’ paura.
Ma ora hai strumenti.
Prossima lezione
Nella prossima lezione costruiremo un piccolo progetto pratico con PostgreSQL.
Progetteremo tabelle.
Creeremo relazioni.
Inseriremo dati.
Eseguiremo query.
Useremo JOIN.
Useremo funzioni aggregate.
E penseremo agli indici.
In altre parole, metteremo insieme i pezzi del corso.
Come montare mobili.
Ma con meno viti mancanti.
Si spera.