Una pagina può essere lenta senza una sola query lenta. Venti prodotti, e per ciascuno una query per la categoria, una per il prezzo e una per l'immagine: sessantuno query dove ne bastavano due. Ognuna impiega due millisecondi e la pagina impiega un secondo e mezzo.
Prima le conti
// at the end of the page
echo count($pdo->query('SHOW SESSION STATUS LIKE "Questions"')->fetchAll());
Oppure legga il numero dalla barra di debug del framework, o dallo slow log con long_query_time a 0 per una sola richiesta. Il numero conta più di qualsiasi tempo preso da solo: sotto 20 è normale, sopra 100 è il difetto.
Che aspetto ha nel codice
// N+1: one query, then one more per row
$products = query('SELECT * FROM products LIMIT 20');
foreach ($products as $p) {
$p->category = query('SELECT * FROM categories WHERE id = ?', $p->category_id);
}
Il rimedio è una join, o una query per tutti gli id
$ids = array_column($products, 'category_id');
$cats = query('SELECT * FROM categories WHERE id IN (' . placeholders($ids) . ')', $ids);
// then match them up in PHP
Due query invece di ventuno, e non peggiora quando la pagina mostra cento prodotti - ed è proprio questa la proprietà che conta.
Dentro un ORM
L'eager loading serve a questo. Il default pigro è comodo e produce esattamente questa forma, e il rimedio è di solito una parola sola.
$products = Product::with('category', 'images')->limit(20)->get();