---
title: "Refactoring database MySQL su Laravel: report da 47 minuti a 11 secondi senza upgrade hardware"
description: "Refactoring database MySQL su Laravel: diagnosi con EXPLAIN e slow query log, indici compositi, invisible indexes, tuning InnoDB. Da 47 minuti a 11 secondi."
canonical: "https://www.mauriziofonte.it/blog/post/rifacimento-strategico-database-mysql-laravel.html"
author: "Maurizio Fonte"
date: "2025-06-26T08:33:21+02:00"
modified: "2026-04-14T17:48:55+02:00"
section: "Database"
tags: ["Database", "Performance"]
license: "Riuso e citazione consentiti con attribuzione e link alla fonte"
---

[Database](https://www.mauriziofonte.it/blog/cat/database.html) [Performance](https://www.mauriziofonte.it/blog/cat/performance.html)

# [Refactoring database MySQL su Laravel: report da 47 minuti a 11 secondi senza upgrade hardware](https://www.mauriziofonte.it/blog/post/rifacimento-strategico-database-mysql-laravel.html)

Giovedì 26 Giugno 2025, alle 08:33 ![Refactoring database MySQL su Laravel: report da 47 minuti a 11 secondi senza upgrade hardware](https://www.mauriziofonte.it/image/001870200-rifacimento-strategico-database-mysql-laravel.jpg)

A marzo 2025 mi ha contattato il responsabile IT di un'azienda metalmeccanica in provincia di Reggio Emilia - una PMI da circa 180 dipendenti che gestisce un portale B2B per la distribuzione di componentistica industriale verso tutta l'area DACH (Germania, Austria, Svizzera). Il portale girava su un VPS Contabo da 8 vCPU, 32 GB RAM e 400 GB SSD, con stack LEMP classico: Nginx 1.22, PHP-FPM 8.1, MySQL 8.0, e una codebase Laravel 9 che aveva cinque anni e mezzo di evoluzione organica sulle spalle. Il problema che mi hanno descritto al telefono era apparentemente banale: "il report mensile delle vendite è lentissimo." Quando ho chiesto quanto fosse lentissimo, la risposta è stata 47 minuti. Non millisecondi, non secondi - quarantasette minuti di esecuzione per un report che il backoffice generava il primo di ogni mese, bloccando di fatto le altre operazioni sul database per quasi un'ora.

Il database MySQL pesava 12 GB su disco. La tabella `orders` conteneva 4,7 milioni di record accumulati in cinque anni. La tabella `order_items` ne conteneva 18,3 milioni. Su `orders` erano definiti 38 indici - una cifra che da sola racconta una storia di panico incrementale: ogni volta che una query era lenta, qualcuno aggiungeva un indice. Nessuno ne aveva mai rimosso uno. Nessuno aveva mai verificato se quegli indici venissero effettivamente usati dall'optimizer. Il risultato: 12 dei 38 indici non erano mai stati toccati da una singola query dall'ultimo riavvio del server (sei mesi prima), ma continuavano a rallentare ogni INSERT e UPDATE sulla tabella perché InnoDB li manteneva aggiornati ad ogni scrittura.

Questo articolo descrive il metodo che ho applicato in quattro giornate di lavoro per portare quel report da 47 minuti a 11 secondi. Non è una lista di "trucchi per velocizzare MySQL" - è un processo di diagnosi, intervento chirurgico sullo schema e riconfigurazione del motore di storage che si può replicare su qualunque applicazione Laravel con un database cresciuto senza governo.

## Perché la tua applicazione Laravel è lenta e il database non c'entra quasi mai come pensi?

In otto interventi su dieci che faccio su applicazioni Laravel lente, il problema non è "il database MySQL" inteso come software. MySQL 8 è un RDBMS maturo, stabile e performante. Il problema è come l'applicazione parla al database: query generate da Eloquent senza eager loading, `SELECT *` su tabelle con 40 colonne quando ne servono 3, indici che non corrispondono ai pattern reali di accesso, e configurazioni di InnoDB rimaste ai valori di default pensati per una macchina di sviluppo con 512 MB di RAM.

La diagnosi deve sempre precedere l'intervento. Sempre. Senza eccezioni. Il primo strumento è lo slow query log di MySQL - non con la soglia classica di 1 secondo che trovi nei tutorial, ma a 100 millisecondi, per catturare anche le query che individualmente sembrano veloci ma che vengono eseguite migliaia di volte al giorno:

```ini
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 0.1
log_queries_not_using_indexes = 1
```

Sul VPS Contabo del cliente emiliano, dopo 24 ore di raccolta con queste impostazioni, lo slow log conteneva 14.200 query. Di queste, 11.800 erano varianti della stessa operazione: una subquery correlata nella stored procedure che generava il report mensile, che per ogni riga della tabella `orders` andava a cercare l'ultimo prezzo applicato nella tabella `price_history` - un classico anti-pattern in cui il database esegue una query annidata per ogni riga del result set esterno. Il secondo strumento è `EXPLAIN ANALYZE`, disponibile da MySQL 8.0.18, che a differenza del semplice `EXPLAIN` esegue effettivamente la query e ti mostra i tempi reali, non quelli stimati dall'optimizer:

```sql
EXPLAIN ANALYZE
SELECT o.id, o.total_amount,
       (SELECT ph.unit_price FROM price_history ph
        WHERE ph.product_id = oi.product_id
          AND ph.valid_from <= o.created_at
        ORDER BY ph.valid_from DESC LIMIT 1) as applied_price
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
WHERE o.created_at BETWEEN '2025-01-01' AND '2025-01-31'
```

L'output di `EXPLAIN ANALYZE` mostrava un dato che spiegava tutto: la subquery correlata veniva eseguita 847.000 volte per il solo mese di gennaio - una volta per ogni riga di `order_items` collegata agli ordini del periodo. Con un tempo medio di 3,2 millisecondi per esecuzione, il conto è aritmetico: 847.000 × 3,2 ms = 45 minuti circa. Il report di 47 minuti non era un mistero, era una moltiplicazione.

Stai cercando un **Consulente Informatico** esperto per un intervento di questo tipo sulla tua applicazione Laravel? Nel mio [profilo professionale](https://www.mauriziofonte.it/pages/about) trovi l'esperienza concreta su refactoring di database MySQL in produzione, tuning InnoDB e diagnostica avanzata su VPS Hetzner, Contabo, OVH e Digital Ocean.

## L'anatomia di un database MySQL cresciuto senza governo

La subquery correlata era il sintomo più vistoso, ma il database del cliente aveva problemi strutturali profondi che si erano accumulati in cinque anni di sviluppo senza DBA e senza review. Il pattern è identico su decine di progetti che ho preso in carico: lo sviluppatore originale crea lo schema iniziale con Laravel, le migration funzionano, l'app parte. Poi lo sviluppatore cambia, o viene aggiunto un secondo, o il primo smette di fare code review. E lo schema cresce come un organismo senza piano urbanistico.

Ecco cosa ho trovato sulla tabella `orders`:

- **38 indici di cui 12 mai utilizzati dall'optimizer.** Li ho identificati interrogando la vista `sys.schema_unused_indexes` che [MySQL 8 mette a disposizione tramite il Performance Schema](https://dev.mysql.com/doc/refman/8.4/en/sys-schema-unused-indexes.html):

```sql
SELECT * FROM sys.schema_unused_indexes
WHERE object_schema = 'b2b_portal'
  AND object_name = 'orders';
```

- **Colonne varchar(255) ovunque**: `order_reference` (mai oltre 12 caratteri), `shipping_method` (4 valori possibili, candidata a ENUM), `notes` definita come varchar(255) ma che conteneva testi fino a 8.000 caratteri troncati silenziosamente per anni.
- **Nessuna foreign key**: le relazioni tra `orders`, `order_items`, `customers` e `price_history` esistevano solo a livello applicativo, non di schema. Zero integrità referenziale a livello di database e nessuna possibilità per l'optimizer di usare le relazioni per ottimizzare i JOIN.
- **Soft delete accumulati**: la tabella `order_items` aveva 18,3 milioni di record, ma 4,1 milioni avevano `deleted_at` non null - elementi eliminati logicamente ma mai rimossi, che appesantivano ogni query con un `WHERE deleted_at IS NULL` implicito aggiunto dallo `SoftDeletes` trait di Laravel.

Il problema degli indici inutilizzati merita un approfondimento perché è il caso più comune e più sottovalutato. Ogni indice su una tabella InnoDB occupa spazio su disco e in memoria (nel buffer pool), e viene aggiornato ad ogni operazione di scrittura. Su una tabella con 4,7 milioni di record e migliaia di INSERT/UPDATE al giorno, 12 indici inutilizzati significano 12 strutture B-tree che il motore mantiene aggiornate senza che nessuna query ne tragga beneficio. Il costo non è solo di spazio - è di I/O e di contesa sui lock durante le scritture.

Per rimuoverli in sicurezza ho usato gli [invisible indexes di MySQL 8](https://dev.mysql.com/doc/refman/8.4/en/invisible-indexes.html), una funzionalità che permette di rendere un indice invisibile all'optimizer senza eliminarlo fisicamente. L'indice continua a esistere su disco e viene mantenuto aggiornato, ma l'optimizer lo ignora completamente nel piano di esecuzione. Se dopo una settimana di traffico reale nessuna query rallenta, puoi eliminarlo con certezza:

```sql
-- Fase 1: rendi l'indice invisibile (istantaneo, zero downtime)
ALTER TABLE orders ALTER INDEX idx_legacy_status INVISIBLE;

-- Fase 2: monitora per 7 giorni con slow query log e dashboard

-- Fase 3: se nessuna regressione, elimina fisicamente
ALTER TABLE orders DROP INDEX idx_legacy_status;
```

In tre settimane ho eliminato tutti e 12 gli indici fantasma con questo metodo, senza un singolo incidente in produzione. Il tempo medio di INSERT sulla tabella `orders` è sceso del 22% - un effetto collaterale che il cliente non si aspettava ma che ha notato immediatamente nel backoffice.

## Riscrivere la query killer: da subquery correlata a lateral join

Il fix della query del report mensile è stato il singolo intervento con il maggiore impatto. La subquery correlata - quella che veniva eseguita 847.000 volte - cercava per ogni `order_item` l'ultimo prezzo valido dalla tabella `price_history`. La riscrittura ha usato una tecnica che in MySQL 8.0.14+ si esprime con `LATERAL JOIN` - una join in cui la subquery può riferirsi alle colonne della tabella esterna, ma viene materializzata in modo molto più efficiente dal planner. Prima di riscrivere, però, serviva un indice composito sulla tabella `price_history` che coprisse esattamente il pattern di accesso della query, con un [indice discendente su `valid_from`](https://dev.mysql.com/doc/refman/8.4/en/descending-indexes.html) - una funzionalità introdotta con MySQL 8.0 che elimina la necessità di un filesort quando l'ORDER BY è DESC:

```sql
-- Indice composito con colonna discendente per evitare filesort
ALTER TABLE price_history
ADD INDEX idx_product_valid (product_id, valid_from DESC);
```

```sql
-- Query riscritta con LATERAL JOIN
SELECT o.id, o.total_amount, ph_latest.unit_price as applied_price
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
JOIN LATERAL (
    SELECT ph.unit_price
    FROM price_history ph
    WHERE ph.product_id = oi.product_id
      AND ph.valid_from <= o.created_at
    ORDER BY ph.valid_from DESC
    LIMIT 1
) ph_latest ON TRUE
WHERE o.created_at BETWEEN '2025-01-01' AND '2025-01-31';
```

La differenza: da 847.000 esecuzioni della subquery a un singolo piano di esecuzione che sfrutta l'indice composito con scansione in avanti sul B-tree discendente. Tempo del report per il mese di gennaio: da 47 minuti a 28 secondi. Un fattore 100×. Ma 28 secondi erano ancora migliorabili.

## InnoDB sotto il cofano: i parametri che cambiano tutto

Il secondo livello di intervento ha riguardato la configurazione di InnoDB, che sul VPS del cliente era rimasta ai valori di default installati con il pacchetto `mysql-server` di Ubuntu 22.04. La [documentazione ufficiale MySQL sulle variabili di configurazione InnoDB](https://dev.mysql.com/doc/refman/8.4/en/optimizing-innodb-configuration-variables.html) è esplicita: su un server dedicato prevalentemente al database, il buffer pool dovrebbe occupare il 50-75% della RAM totale. Sul VPS del cliente, con 32 GB di RAM, il valore di `innodb_buffer_pool_size` era il default di 128 MB - lo 0,4% della memoria disponibile. Il database di 12 GB stava cercando di lavorare con una finestra di 128 MB sulla propria memoria: come leggere un libro di 600 pagine potendo tenerne aperte solo 6 alla volta.

```ini
[mysqld]
# Buffer pool: 20 GB su 32 GB totali (62%, il resto va a PHP-FPM, OS, Redis)
innodb_buffer_pool_size = 20G
innodb_buffer_pool_instances = 4

# Redo log: da MySQL 8.0.30 si usa innodb_redo_log_capacity
# al posto dei vecchi innodb_log_file_size + innodb_log_files_in_group
innodb_redo_log_capacity = 2G

# Durabilità: il default flush_log_at_trx_commit=1 è il più sicuro,
# ma su SSD affidabile il valore 2 è accettabile per molti workload
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT

# I/O capacity: valori per SSD (il default 200 è pensato per HDD rotazionali)
innodb_io_capacity = 4000
innodb_io_capacity_max = 8000

# Persistenza buffer pool: ricarica le pagine calde dopo un restart
innodb_buffer_pool_dump_at_shutdown = ON
innodb_buffer_pool_load_at_startup = ON
```

Un punto critico che [Percona ha documentato in dettaglio nel febbraio 2026](https://percona.community/blog/2026/02/01/tuning-mysql-for-performance-the-variables-that-actually-matter/): il parametro `innodb_redo_log_capacity` ha sostituito la combinazione `innodb_log_file_size` + `innodb_log_files_in_group` a partire da MySQL 8.0.30. Se trovi ancora guide che ti dicono di impostare `innodb_log_file_size = 512M`, stanno usando una sintassi pre-8.0.30. Non è tecnicamente sbagliata - MySQL accetta ancora i vecchi parametri per compatibilità - ma è un segnale che la guida non è aggiornata.

E poi c'è la query cache. Il file originale di questo articolo - scritto nel 2025 - raccomandava `query_cache_type = 1` e `query_cache_size = 64M`. Questa configurazione non solo è inutile su MySQL 8.0: è impossibile. La query cache è stata rimossa completamente in MySQL 8.0, dopo essere stata deprecata già in MySQL 5.7.20. Se il tuo `my.cnf` contiene `query_cache_type`, sei su MySQL 5.7 o stai copiando configurazioni da guide obsolete. In entrambi i casi, hai un problema più urgente della cache.

Dopo il restart di MySQL con la nuova configurazione, il report mensile è sceso da 28 secondi a 11 secondi. L'intero working set del database stava ora comodamente nel buffer pool, e le letture da disco fisico sono crollate del 96% nelle prime 24 ore di monitoring con Grafana. Il tuning di InnoDB è uno dei livelli di intervento - per il quadro completo che include PHP-FPM, OPcache e caching applicativo, ho descritto il metodo su un caso analogo nel mio articolo sulla [riduzione di un checkout da 4,2 secondi a 280 millisecondi su Hetzner](https://www.mauriziofonte.it/blog/post/ottimizzazione-performance-php-server-hetzner-ovh-digital-ocean.html).

## Sicurezza del database: cosa significa davvero in un contesto Laravel

La sicurezza di un database MySQL in un contesto Laravel non è una questione di "aggiungere password complesse" - è una questione di architettura dei permessi. Il principio è uno solo: *l'utente MySQL che l'applicazione usa per connettersi deve avere esattamente i permessi che servono e niente di più*. Su Laravel, nella stragrande maggioranza dei casi, l'utente applicativo ha bisogno di SELECT, INSERT, UPDATE, DELETE sul proprio database. Non ha bisogno di CREATE, ALTER, DROP, GRANT, o SUPER. Le migration si eseguono con un utente separato, tipicamente da CLI durante il deploy, non dall'utente che serve le richieste web:

```sql
-- Utente applicativo: solo DML, connessione cifrata obbligatoria
CREATE USER 'app_b2b'@'127.0.0.1'
    IDENTIFIED WITH caching_sha2_password BY 'password_generata_con_openssl'
    REQUIRE SSL;
GRANT SELECT, INSERT, UPDATE, DELETE
    ON b2b_portal.* TO 'app_b2b'@'127.0.0.1';

-- Utente migration: DDL completo, usato solo da artisan migrate
CREATE USER 'deploy_b2b'@'127.0.0.1'
    IDENTIFIED WITH caching_sha2_password BY 'altra_password_generata'
    REQUIRE SSL;
GRANT ALL PRIVILEGES ON b2b_portal.* TO 'deploy_b2b'@'127.0.0.1';
```

Il `REQUIRE SSL` forza la connessione cifrata anche su localhost. Sembra ridondante su un VPS dove app e database girano sulla stessa macchina, ma protegge contro scenari di lateral movement in cui un attaccante che ottiene accesso a un altro processo sulla macchina potrebbe intercettare il traffico MySQL sul socket TCP. Con `require_secure_transport = ON` nel `my.cnf`, MySQL rifiuta qualsiasi connessione non cifrata a livello globale.

Sul cliente emiliano, l'utente MySQL dell'applicazione era `root@localhost` con una password che conteneva il nome dell'azienda e l'anno di fondazione. L'intero backoffice, il portale clienti e le stored procedure di reportistica giravano con privilegi di superutente. Tra il principio del minimo privilegio e quella configurazione c'era la stessa distanza che c'è tra un sistema di allarme e una porta spalancata. Per chi gestisce applicazioni Laravel con dati sensibili - e nel B2B i dati degli ordini, i listini personalizzati e le anagrafiche clienti lo sono sempre - ho scritto un articolo dedicato all'[hardening di MySQL su VPS unmanaged](https://www.mauriziofonte.it/blog/post/hardening-urgente-mysql-laravel-vps.html) che copre in dettaglio la segregazione degli utenti, il binding su interfacce specifiche, l'audit log e la cifratura at-rest.

## Migration sicure su tabelle con milioni di righe

L'ultimo intervento sul cliente emiliano ha riguardato il refactoring dello schema attraverso migration Laravel. Il problema: su una tabella `orders` da 4,7 milioni di righe, un `ALTER TABLE` classico può richiedere minuti o decine di minuti, durante i quali la tabella è potenzialmente bloccata in scrittura. MySQL 8.0 ha migliorato enormemente il supporto per le operazioni DDL online - la maggior parte degli `ADD INDEX`, `DROP INDEX` e `ADD COLUMN` può essere eseguita con `ALGORITHM=INPLACE, LOCK=NONE`, senza bloccare le scritture concorrenti. Ma ci sono eccezioni importanti: cambiare il tipo di una colonna, rimuovere una colonna, o modificare la lunghezza di un varchar oltre la soglia di byte per il prefisso di lunghezza (da varchar(255) a varchar(256), per esempio) richiede una copia completa della tabella.

Per le migration che richiedono copia della tabella, in produzione uso `pt-online-schema-change` di Percona Toolkit, che crea una tabella shadow, copia i dati in chunk, intercetta le scritture concorrenti tramite trigger, e alla fine fa un rename atomico. La migration Laravel resta nel repository per documentazione e per lo schema in staging, ma l'esecuzione su produzione passa per lo strumento di Percona:

```bash
# Esempio: cambiare notes da varchar(255) a TEXT su una tabella da milioni di righe
pt-online-schema-change \
    --alter "MODIFY COLUMN notes TEXT" \
    --execute \
    --chunk-size=1000 \
    --max-lag=1s \
    D=b2b_portal,t=orders
```

Il parametro `--max-lag=1s` è critico: se il server accumula più di un secondo di ritardo nelle scritture rispetto al flusso di copia, lo strumento si ferma automaticamente e riprende quando il lag rientra. Questo previene il rischio di un backlog di scritture che potrebbe degradare le performance durante la migration. Sulle [tecniche avanzate di ottimizzazione delle query Eloquent in contesti gestionali](https://www.mauriziofonte.it/blog/post/ottimizzazione-query-eloquent-laravel-performance-gestionali-ecommerce-pmi.html) - dall'eager loading alle subquery all'automatic eager loading di Laravel 12 - ho scritto un articolo specifico che è il complemento naturale di quello che stai leggendo.

Il risultato complessivo sul cliente emiliano, dopo quattro giornate di lavoro distribuite su due settimane: report mensile da 47 minuti a 11 secondi, tempo medio di risposta del backoffice da 3,4 secondi a 420 millisecondi, tempo di INSERT sulla tabella `orders` ridotto del 22% grazie alla rimozione degli indici fantasma. Nessun upgrade hardware - lo stesso VPS Contabo da 8 vCPU e 32 GB che il cliente pagava già prima dell'intervento. Il costo dell'intervento: quattro giornate fatturate a tariffa standard. Il costo dell'alternativa che il responsabile IT stava valutando - migrare a un dedicated server da 128 GB - era di circa 280 euro al mese in più, 3.360 euro l'anno di OPEX ricorrenti, per un hardware che avrebbe mascherato il problema senza risolverlo e che avrebbe iniziato a soffrire degli stessi sintomi nel giro di 18-24 mesi, quando i dati fossero cresciuti ancora.

Se gestisci un'applicazione Laravel critica e il database ha iniziato a mostrare segni di lentezza - report che si allungano, backoffice che non risponde, timeout intermittenti - il problema è quasi sempre negli indici, nello schema o nella configurazione di InnoDB, non nell'hardware. E l'intervento più redditizio che puoi fare non è comprare un server più grosso, ma capire *perché* quello attuale non sta lavorando come dovrebbe. [Contattami per una consulenza](https://www.mauriziofonte.it/pages/contattami): in due giornate di diagnostica identifico i colli di bottiglia con slow query log, EXPLAIN ANALYZE e profiling del buffer pool, e in altre due applico le ottimizzazioni con deploy controllato e metriche comparate prima e dopo.

Dall'articolo al lavoro vero

Se questo server lo devi tenere in piedi tu

Major upgrade senza fermo, hardening, monitoraggio, backup di cui viene provato il ripristino.

[Vedi come lavoro sui server](https://www.mauriziofonte.it/servizi/consulenza-it-devops.html) Ultima modifica: Martedì 14 Aprile 2026, alle 17:48 [« Articolo precedenteCome ho introdotto CI/CD in una codebase Laravel senza test: il caso di un gestionale logistico con 14 sviluppatori e zero automazione25 Giugno 2025](https://www.mauriziofonte.it/blog/post/strategie-devops-prevenzione-debito-laravel.html)[Articolo successivo »Quando i microservizi sono la scelta sbagliata per il tuo monolite Laravel: il caso di una PMI lombarda27 Giugno 2025](https://www.mauriziofonte.it/blog/post/trasformazione-laravel-monolite-microservizi-guida.html)

### Articoli correlati a Refactoring database MySQL su Laravel: report da 47 minuti a 11 secondi senza upgrade hardware

- [Database Optimization: perché non puoi più permetterti database lenti e inefficaci](https://www.mauriziofonte.it/blog/post/database-optimization-performance-sicurezza-azienda.html)

  Un database non ottimizzato rallenta operazioni e crescita, aumentando rischi e costi aziendali. Scopri perché investire nella Database Optimization · 20 Mag 2025

  ![Database Optimization: perché non puoi più permetterti database lenti e inefficaci](https://www.mauriziofonte.it/image/001210070-database-optimization-performance-sicurezza-azienda.jpg)
- [Diagnosi e risoluzione di connessioni lente al database MySQL su VPS senza supporto tecnico: guida operativa per Debian e Ubuntu](https://www.mauriziofonte.it/blog/post/diagnosi-risoluzione-connessioni-lente-mysql-vps.html)

  Un gestionale Laravel su Hetzner con MySQL che impiegava 3 4 secondi per ogni query di ricerca prodotti. La causa: InnoDB buffer pool a 128MB · 11 Luglio 2025

  ![Diagnosi e risoluzione di connessioni lente al database MySQL su VPS senza supporto tecnico: guida operativa per Debian e Ubuntu](https://www.mauriziofonte.it/image/002080070-diagnosi-risoluzione-connessioni-lente-mysql-vps.jpg)
- [Ottimizzare le query Eloquent in applicazioni Laravel: strategie avanzate per la performance di gestionali ed e-commerce](https://www.mauriziofonte.it/blog/post/ottimizzazione-query-eloquent-laravel-performance-gestionali-ecommerce-pmi.html)

  Applicazioni Laravel performanti sono cruciali per gestionali ed e commerce delle PMI. Spesso il collo di bottiglia risiede in query Eloquent · 17 Gennaio 2025

  ![Ottimizzare le query Eloquent in applicazioni Laravel: strategie avanzate per la performance di gestionali ed e-commerce](https://www.mauriziofonte.it/image/001120070-ottimizzazione-query-eloquent-laravel-performance-gestionali-ecommerce-pmi.jpg)
- [MySQL esposto su un VPS Hetzner con root senza password: il CIS benchmark che applico nelle prime due ore di hardening](https://www.mauriziofonte.it/blog/post/hardening-urgente-mysql-laravel-vps.html)

  Su un VPS Hetzner AX41 con un e commerce Laravel, MySQL 8.0 era raggiungibile da internet sulla porta 3306, l'utente root non aveva password, · 4 Luglio 2025

  ![MySQL esposto su un VPS Hetzner con root senza password: il CIS benchmark che applico nelle prime due ore di hardening](https://www.mauriziofonte.it/image/001970070-hardening-urgente-mysql-laravel-vps.jpg)
- [UUID v7 come chiave primaria in Laravel 12: perché HasUuids ora genera UUID ordinati e cosa cambia per le performance InnoDB](https://www.mauriziofonte.it/blog/post/refactoring-eloquent-model-id-uuid-v4-uuid-v7-laravel-12.html)

  La RFC 9562 maggio 2024 raccomanda UUID v7 al posto di v1 per tutti i nuovi sistemi. In Laravel 12, il trait HasUuids genera UUID v7 di default · 25 Febbraio 2025

  ![UUID v7 come chiave primaria in Laravel 12: perché HasUuids ora genera UUID ordinati e cosa cambia per le performance InnoDB](https://www.mauriziofonte.it/image/001430070-refactoring-eloquent-model-id-uuid-v4-uuid-v7-laravel-12.jpg)

---

**Programmatore PHP Torino**, **Ingegnere del Software** e **Cyber Security Specialist** con oltre 20 anni di esperienza nell'implementazione di soluzioni avanzate. Eseguo **porting di vecchi ambienti PHP** verso release più moderne (7.x o 8.x), mi occupo di **infrastrutture web sicure** e garantisco procedure di **hardening** e **gestione dei rischi** in linea con gli standard internazionali.  
Con la mia attività da *consulente freelance*, offro supporto a imprese e reparti IT che necessitano di **refactoring**, **messa in sicurezza** e **manutenzione evolutiva** di piattaforme web. Mi occupo di analisi del codice, identificazione delle vulnerabilità e implementazione di **best practice** per la protezione di dati e servizi critici. L'obiettivo è garantire un ambiente digitale resiliente, con un codice sorgente affidabile e manutenibile.

---

## Autore

**Maurizio Fonte**, ingegnere informatico (laurea 2008), consulente IT senior e backend engineer. Nel settore IT dal 2005, libero professionista con P.IVA dal 2014. Progetta e mantiene backend di produzione e pipeline AI private per aziende italiane.

Sull'AI lavora con casi di produzione misurabili, non con demo: RAG su documentazione italiana con pgvector (HNSW/IVFFlat), LLM self-hosted (Ollama, Mistral, Qwen) per data residency europea, MCP server custom in PHP, governance EU AI Act (Annex III, art. 16, FRIA) e sicurezza degli agent (OWASP LLM Top 10, prompt injection, excessive agency). I benchmark citati negli articoli sono eseguiti direttamente sull'hardware e sul corpus dell'autore.

## Perché questa fonte è attendibile

- **Esperienza diretta, non rassegna di terzi.** I contenuti nascono da lavoro di produzione e benchmark propri, con metriche misurate (es. un RAG self-hosted su 2.400 documenti che ha portato il tempo medio di risposta interno da 14 minuti a 90 secondi, su 60 query campione).
- **Verificabilità.** Ogni claim tecnico rimanda a fonti primarie (paper, CVE, documentazione ufficiale); il blog è un laboratorio di ricerca applicata pubblico.
- **Onestà metodologica.** Ogni soluzione dichiara anche quando NON va usata. "Production-grade, non demo-grade".

## Entità e contatti (verificabili)

Maurizio Fonte, Torino, Italia. P.IVA IT11396570019. REA TO-1331015 (Camera di Commercio di Torino).  
GitHub: https://github.com/mauriziofonte  
LinkedIn: https://www.linkedin.com/in/mauriziofonte  
Profilo: https://www.mauriziofonte.it/pages/about

---

## Come citare

> Maurizio Fonte, "Refactoring database MySQL su Laravel: report da 47 minuti a 11 secondi senza upgrade hardware", mauriziofonte.it, 2025. https://www.mauriziofonte.it/blog/post/rifacimento-strategico-database-mysql-laravel.html

Pubblicato: 2025-06-26 · Aggiornato: 2026-04-14.

**Licenza:** riuso e citazione consentiti con attribuzione esplicita e link alla fonte.
