 
Decidere di migrare da MySQL a PostgreSQL non è un capriccio né un vezzo da hipster tecnologici. Non è una bandierina da sventolare facendo finta di essere “enterprise”. È una decisione che matura quando capisci che il database non è un componente di contorno ma il cuore pulsante dell’applicazione. Un cuore che, se non in grado di reggere complessità e carico, porta tutto il resto a collassare in silenzio prima, nel panico poi. Quando inizi davvero a vedere il database per quello che è, smetti di ragionare in termini di “cosa è più diffuso” e inizi a ragionare in termini di “cosa mi permette di non dover reingegnerizzare la mia architettura tra due anni”.
Per anni ho difeso MySQL. L’ho celebrato per la sua immediatezza, ho sostenuto che “basta saperlo usare bene”. L’ho usato ovunque, dal piccolo gestionale improvvisato nella notte pre-rilascio fino alla piattaforma che avrebbe dovuto scalare per migliaia di utenti contemporanei. E per tanto tempo mi ha fatto credere che fosse all’altezza di tutto. Poi sono arrivati i progetti veri: dataset che non stanno più comodi in RAM, workflow transazionali che non perdonano sbavature, modelli dati che non sono semplici tabelle ma ecosistemi logici che rappresentano fenomeni complessi del mondo reale. Lì MySQL, pur volendo bene al suo spirito semplice, ti mostra che non è nato per certe profondità. Ti accorgi che stai forzando la sua natura, che stai cercando di ottenere rigore da uno strumento che per filosofia nasce permissivo e accomodante. PostgreSQL invece sì. È nato per la coerenza e l’esattezza prima ancora che per la velocità. Questo non lo impari nella documentazione: te lo insegna ogni query che scrivi quando il mondo attorno alla tua applicazione diventa più serio e non accetta più approssimazioni. Quando devi fare refactoring dei tuoi modelli e scopri che vincoli, tipi avanzati e transazioni solide non sono “nice to have” ma ciò che ti salva la pelle.
Perché abbandonare MySQL se “funziona”? Perché “funziona” non significa “ti permette di dormire tranquillo”. MySQL funziona finché il mondo è semplice. Finché non devi inseguire dipendenze logiche complesse, versionamenti dei dati, controlli di integrità che devono essere più intelligenti di un semplice foreign key, oppure gestire migrazioni che non devono spezzare la produzione. Anzi, sembra funzionare anche quando diventa complesso, ma quella sensazione è ingannevole. La sua permissività è confortevole finché sei nella fase “tutto è possibile”. Poi inizi a inseguire bug silenziosi, inconsistenze, comportamenti diversi tra ambienti, coercizioni implicite che ti fanno cadere nello stesso errore due volte, tipi che si trasformano quando non devono, query che sembrano corrette finché non scopri il caso limite che rompe tutto. E capisci che non sei tu che non sei abbastanza bravo: è l’assenza di freni che ti mette nei guai. È la libertà senza guida che ti porta a costruire castelli sulla sabbia.
PostgreSQL, al contrario, non ti fa sconti. Se sbagli ti tira una ciabatta digitale. All’inizio lo odi, perché ti costringe a pensare in modo rigoroso, perché non ti permette scorciatoie, perché ti obbliga a modellare in modo serio. Poi ti rendi conto che quella rigidità è uno specchio che ti rimanda indietro la qualità del tuo lavoro. Ti accorgi che stai scrivendo codice più pulito, schema più coerenti, applicazioni più prevedibili e future-proof. Che non stai più sperando che qualcosa “vada bene”, ma sai perché va bene. E inizi ad apprezzare quella disciplina forzata come apprezzi il personal trainer che ti fa vomitare al terzo squat ma poi ti guarda soddisfatto mentre sollevi più peso di quanto pensavi possibile. PostgreSQL non ti accarezza, ti forma. E quando lo capisci davvero, non torni più indietro, perché hai visto cosa significa costruire architetture che non tremano al primo refactoring o al primo picco di traffico.
La migrazione non è una moda. È un passaggio di maturità tecnica. È il punto in cui smetti di accontentarti del “basta che gira” e inizi a pretendere solidità strutturale. In cui smetti di pensare in termini di tool e inizi a pensare in termini di responsabilità progettuale. È un salto che non fai per sentirti più bravo, ma perché ormai sai che non puoi permetterti di essere superficiale. E quando arrivi lì, PostgreSQL non è più una possibilità: è la scelta inevitabile.
Tipi, dati e struttura: dove PostgreSQL ti raddrizza la schiena
MySQL ti lascia fare errori che non vedi. PostgreSQL ti costringe a vedere la realtà. È un database che, semplicemente, non tollera l’approssimazione mentale. È come passare da una palestra “libera” dove ti arrangi con manubri storti e panche che traballano a un centro di preparazione atletica dove ogni movimento è preciso, misurato, calibrato. Non è questione di essere snob tecnologici: è questione di imparare a non costruire software sopra supposizioni e compromessi che prima o poi presentano il conto.
Esempio concreto: i booleani. In MySQL ti auto-convinci che TINYINT(1) sia “lo stesso”. Ti racconti che tanto 0 e 1 sono valori chiari, che alla fine il codice farà la traduzione, che il tipo è solo una formalità. E intanto ti dimentichi del giorno in cui ti sei ritrovato un “3” dentro un campo che doveva rappresentare vero o falso, oppure di quella query che ha trattato '0', 0 e NULL in modo ambiguo facendoti impazzire per ore a capire perché la tua logica non rispecchiava i dati reali.
In PostgreSQL scopri che il mondo non è fatto per scorciatoie. Boolean è boolean. Vero o falso. Fine. Non esiste un limbo interpretativo dove il database prova a indovinare cosa volevi dire. Ti ritrovi costretto a prendere decisioni esplicite, a definire regole nette, a non nascondere sotto il tappeto quei dettagli che MySQL maschera con la sua “elasticità”. È scomodo all'inizio, perché toglie l’illusione della semplicità, ma poi ti rendi conto che quella chiarezza mentale ti salva da interi capitoli di debugging, refactoring e patch disperate.
Quando lavori con PostgreSQL capisci che i tipi non sono un fastidio, sono una forma di contratto mentale e architetturale. E quel contratto significa una cosa: quello che salvi nel database è ciò che intendevi salvare davvero. Non quello che il database ha interpretato per te. Questa è la differenza tra scrivere codice che “va” e costruire sistemi che resistono al tempo, ai feature toggle, alle migrazioni, ai contributi di altri sviluppatori, ai casi limite che nessuno aveva pensato. È una forma di serietà tecnica che non puoi più abbandonare una volta assaggiata.
MySQL:
CREATE TABLE utenti (
  id INT AUTO_INCREMENT PRIMARY KEY,
  nome VARCHAR(255),
  metadata JSON,
  attivo TINYINT(1) DEFAULT 1
);
PostgreSQL:
CREATE TABLE utenti (
  id SERIAL PRIMARY KEY,
  nome TEXT,
  metadata JSONB,
  attivo BOOLEAN DEFAULT TRUE
);
E quando importi dati sporchi, niente più senso di onnipotenza:
UPDATE utenti
SET attivo =
  CASE attivo
    WHEN 1 THEN TRUE
    WHEN 0 THEN FALSE
  END;
Sembra banale? È un cambio di mentalità: non stai accettando sporcizia nei dati perché “tanto funziona”.
JSONB: dove smetti di simulare flessibilità e inizi ad averla
MySQL ha introdotto JSON e tutti abbiamo detto: “che bello, ora sono moderno”. Poi hai provato a indicizzare, filtrare nidificazioni, fare query complesse e hai scoperto che era moderno tipo come un motorino truccato del 2002: divertente, ma se provi a farlo correre davvero prende fuoco. Lì ti rendi conto che sì, tecnicamente puoi memorizzare dati semi-strutturati, ma appena inizi a usarli sul serio ti scontri con limiti che non sono semplici dettagli tecnici: sono muri contro cui sbatti la faccia quando cerchi di costruire sistemi che devono crescere, evolversi, rispondere a esigenze applicative sofisticate. In MySQL il JSON lo usi con la sensazione costante di essere un ospite tollerato, uno che sta chiedendo troppo. Ti senti dire “ok, puoi farlo”, ma sottovoce il motore sogghigna, perché sa che il giorno in cui avrai bisogno di prestazioni o consistenza ti ritroverai a inseguire trick, scorciatoie e indicizzazioni creative che sembrano più rituali sciamanici che ingegneria.
JSONB in PostgreSQL è un tipo di dato nativo pensato per contenere documenti JSON in maniera binaria e ottimizzata. A differenza del semplice JSON, che resta una stringa e richiede parsing ogni volta che lo leggi, JSONB viene memorizzato già strutturato, pronto per interrogazioni efficienti e indicizzazioni. Questo significa che puoi filtrare, aggregare e manipolare dati semi-strutturati con performance simili a quelle dei tipi tradizionali.
GIN (Generalized Inverted Index) è un tipo di indice pensato per ricerche rapide su dati complessi, come array, JSONB, testi e documenti full-text. In pratica crea un indice invertito: per ogni valore o chiave presente nel documento, GIN memorizza dove comparire. Questo permette query su JSONB che altrimenti richiederebbero scansioni complete della tabella, rendendo operazioni come @>, ? o ?& estremamente più veloci.
In combinazione: memorizzando dati in JSONB e indicizzandoli con GIN, PostgreSQL consente di fare query sofisticate su dati semi-strutturati senza sacrificare performance, offrendo flessibilità simile a un NoSQL, ma dentro un contesto relazionale solido.
In PostgreSQL parli con JSONB e ti risponde con rispetto. Non devi supplicare. Non devi inventarti architetture parallele per poter interrogare i dati in modo serio. Puoi creare indici GIN, puoi filtrare strutture annidate senza sentirti un illusionista, puoi combinare JSONB con tipi forti e vincoli tradizionali senza scendere a compromessi. E soprattutto non ti senti mai in colpa per aver scelto una strada flessibile: PostgreSQL non ti punisce per voler modellare realtà complesse, ti accompagna. Capisci che JSON non è una concessione per sembrare moderni, è un mattone nativo della piattaforma. Puoi usarlo per prototipi rapidi, per sistemi dinamici, per esigenze che richiedono evoluzione del modello senza riprogettare metà schema, e nel tempo non diventa un debito tecnico che ti mangia vivo.
Questa differenza è cruciale: in MySQL usi JSON sperando che “vada tutto liscio”, in PostgreSQL lo usi sapendo che puoi fidarti. Il primo sembra offrirti libertà, ma ti carica responsabilità silenziose che scopri troppo tardi. Il secondo ti dà libertà con fondamenta solide, e quando inizi a scrivere query complesse e vederle rispondere come ti aspetti, capisci che non è solo una feature: è un modo diverso di progettare dati, più maturo, più reale, più adatto a costruire software che sopravvive al tempo e alle mode del momento.
Query su campo JSON:
SELECT *
FROM prodotti
WHERE dettagli ->> 'categoria' = 'elettronica';
JSON annidato:
SELECT *
FROM ordini
WHERE dettagli -> 'spedizione' ->> 'stato' = 'consegnato';
Chiave presente:
SELECT *
FROM ordini
WHERE dettagli ? 'spedizione';
Indice GIN, roba seria:
CREATE INDEX idx_ordini_dettagli_gin
ON ordini USING GIN (dettagli);
Qui non stai giocando con un addon improvvisato: stai usando una feature di prima classe.
Full-text search con lemmatizzazione
MySQL cerca parole. PostgreSQL capisce lingue. E questo vale oro nelle piattaforme editoriali, nelle app che ragionano su testi, in tutto ciò che deve trovare contenuti sensati e non solo stringhe uguali. Cercare “cane” e ottenere risultati dove compare esattamente quella sequenza di caratteri è un esercizio da database scolastico; riconoscere che “cani”, “cagnolino”, “cagna” e persino una frase che descrive un animale domestico senza nominarlo esplicitamente appartengono allo stesso campo semantico è un altro livello di consapevolezza, che non puoi fingere con LIKE e qualche indice buttato lì.
Quando inizi a lavorare con contenuti reali ti rendi conto che la full-text search non è un optional carino da tenere nel portafoglio delle feature. È la base della pertinenza delle risposte, della qualità dell’esperienza utente, della capacità di un sistema di surfare sul linguaggio naturale anziché annegare in esso. MySQL ti dà un full-text rudimentale, che funziona finché ti interessa sapere se una parola appare. PostgreSQL invece porta in tavola la lemmatizzazione, gli operatori logici evoluti, i dizionari linguistici, la possibilità di capire contesto e forma, non solo caratteri. È pensato per affrontare testi vivi e complessi, non elenchi statici.
Questo livello di profondità cambia radicalmente come progetti le applicazioni. Non devi più reinventare la semantica nel livello applicativo, non devi più affidarti a librerie esterne che fanno il lavoro sporco e poi rimandano un risultato che il tuo database non sa comprendere a pieno. Con PostgreSQL puoi delegare parte dell’intelligenza al livello dei dati, costruire query che non si limitano a pescare stringhe ma interpretano significati, modulare risultati, ponderarli, renderli pertinenti. E quando inizi a costruire sistemi che trattano il linguaggio come una materia viva, non torni più indietro. Perché improvvisamente smetti di cercare parole e inizi davvero a trovare informazioni.
Preparazione:
ALTER TABLE articoli
ADD COLUMN documento_fts tsvector;
UPDATE articoli
SET documento_fts = to_tsvector('italian', titolo || ' ' || contenuto);
Indice:
CREATE INDEX idx_articoli_fts_gin
ON articoli USING GIN (documento_fts);
Query con ranking:
SELECT titolo, ts_rank(documento_fts, query) AS rank
FROM articoli, to_tsquery('italian', 'prestazione & database') AS query
WHERE documento_fts @@ query
ORDER BY rank DESC;
Trigger:
CREATE FUNCTION articoli_fts_trigger() RETURNS trigger AS $$
BEGIN
  NEW.documento_fts :=
    to_tsvector('italian', NEW.titolo || ' ' || NEW.contenuto);
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_articoli_fts
BEFORE INSERT OR UPDATE ON articoli
FOR EACH ROW EXECUTE FUNCTION articoli_fts_trigger();
Questa roba non è “più feature”. È più software.
ORM, compatibilità e la triste verità su WordPress
Molti ORM nascono su MySQL e poi “aggiungono supporto Postgres”. Questo lo senti subito quando fai ottimizzazione o quando certi operatori sembrano spariti nel nulla. Non è colpa di Postgres. È l’ecosistema che sta crescendo e alcuni strumenti stanno ancora limando gli angoli. La verità è che, per anni, la maggior parte delle applicazioni web nasceva con un presupposto quasi automatico: database relazionale = MySQL. E così gli ORM hanno modellato le loro fondamenta su quel paradigma, sulle sue semplificazioni, sui suoi compromessi. Quando poi arrivi a PostgreSQL e vuoi usare cose come jsonb_set, ILIKE, ->> o @>, ti accorgi che alcuni layer applicativi non sono ancora pronti a trattare il database come un protagonista. Non è un problema tecnico vero e proprio: è una fase di transizione culturale.
Stiamo assistendo a un cambio di mentalità dove il modello dati non è più un “contenitore comodo” ma una parte attiva del design. E quando costruisci software con questa consapevolezza, inizi a scegliere librerie, ORM e framework non in base alla comodità istantanea, ma in base alla loro capacità di esprimere tutta la potenza del motore che sta sotto. Questo significa che, sì, ogni tanto devi sporcarti le mani. Devi usare query raw, devi capire il planner, devi sapere cosa fa un indice GIN o un GIST e perché. Ma il risultato è software più solido, più performante e più coerente nel tempo. È crescita, non sofferenza inutile.
E WordPress? Non ci giriamo attorno: WordPress = MySQL/MariaDB. Vuoi Postgres? Cambia strumento. Non si forza un ecosistema intero per moda. WordPress ha costruito il suo regno su quel database, ha plugin, hoster, cache layer, backup pipeline e tool nati e ottimizzati per quella combinazione. Pretendere che “dovrebbe supportare Postgres” è come lamentarsi perché una bici non parte con una chiave: stai chiedendo a un mezzo progettato in un’epoca e con un obiettivo preciso di trasformarsi in qualcosa che non è.
Se ti serve Postgres per un progetto WordPress, la domanda giusta non è “come faccio ad adattare WordPress?” ma “perché sto insistendo con WordPress?”. O accetti il suo stack e ci vivi bene, o fai una scelta diversa. Pretendere che un intero ecosistema cambi solo per seguire il tuo mindset è ingenuo. La tecnologia, in queste situazioni, non premia la nostalgia né l’ideologia: premia la lucidità. Quando la base dati è un fattore strategico, scegli la piattaforma che lo riconosce. Quando la piattaforma è WordPress, la base dati è MySQL. E va bene così.
CTE ricorsive: quando cominci a pensare da architetto
Ecco dove la magia di PostgreSQL diventa evidente: le CTE ricorsive non sono solo uno strumento elegante, sono un modo per pensare da architetto. Non più query che sbattono contro limiti di join o che ti obbligano a pullare tutto in memoria e processare nel codice. Con una CTE ricorsiva puoi modellare gerarchie, alberi, grafi direttamente nel database, dove il motore sa gestirle al meglio. È uno di quei momenti in cui capisci che non stai più “costruendo sopra un DB”, ma stai “progettando con il DB”.
WITH RECURSIVE categorie_tree AS (
  SELECT id, nome, parent_id
  FROM categorie
  WHERE parent_id IS NULL
  UNION ALL
  SELECT c.id, c.nome, c.parent_id
  FROM categorie c
  INNER JOIN categorie_tree ct ON ct.id = c.parent_id
)
SELECT * FROM categorie_tree;
In MySQL per anni te la sognavi. Ora c’è pure lì, ma quando l’hai in Postgres da tempo ti chiedi: “perché ho aspettato così tanto?”
Conclusione
PostgreSQL non è solo un database più potente: è un ambiente che ti forma. Abbandoni l’idea di “vediamo se funziona” e inizi a ragionare come qualcuno che costruisce sistemi pensati per durare, scalare e non implodere alle prime richieste difficili.
Non è un salto di tecnologia. È un salto mentale. E quando ci arrivi, ti vergogni un po’ di quanto tempo sei rimasto dall’altra parte. Va bene così: crescere nel software è anche questo.
 
  