Qual è un buon modo semplice per combattere il problema n + 1?

6

Sto cercando di capire meglio le prestazioni in PHP. Un problema a cui sto pensando è il problema n + 1. Con n + 1 intendo qualcosa del genere:

$posts = Posts::getPosts();

foreach($posts as $post) {
  $comments = Comments::getComments(array('post_id' => $post->id));
  // do something with comments..
}

È abbastanza inefficiente dato che dobbiamo fare molte domande per ogni post per ottenere i commenti.

È qualcosa di simile meglio? È più codice PHP, ma verranno eseguite solo due query:

$posts = Posts::getPosts();

// get the ids into an array
$post_ids = array();
foreach($posts as $post) {
  array_push($post_ids, $post->id);
}

// get comments for ALL posts in one query by passing array of post ids (post_id IN (...))
$comments = Comments::getComments(array('post_id' => $post_ids));

// map comments to posts
foreach($posts as $key => $post) {
  foreach($comments as $comment) {
    if($post->id == $comment->post_id) {
      $post->pushComment($comment);
    }
  }
}

foreach($posts as $post) {
  $comment = $post->comments;
  // do something with comments..
}

Questo è molto più PHP e anche un po 'disordinato, ma stavolta sto usando solo due query (una per i post, l'altra per il recupero di TUTTI i commenti di quei post in una query). Questa è una buona proposta per affrontare il problema n + 1 in PHP?

Inoltre, in che modo i framework generalmente trattano questo sotto il cofano?

    
posta Martyn 09.07.2014 - 06:40
fonte

4 risposte

4

Il tuo approccio originale è caricamento lento mentre il tuo codice modificato è carico di caricamento .

Hai assolutamente ragione che il caricamento ansioso è più efficiente nella tua situazione. Nella maggior parte dei casi, ridurre al minimo il numero di query è la cosa migliore che puoi fare per velocizzare la tua app. Se stavi solo guardando uno dei post, il caricamento lento sarebbe più veloce.

La maggior parte degli ORM (almeno, tutti i migliori!) supportano il caricamento pigro e avido. Ad esempio, SQLAlchemy ha un ampio supporto. Stavi chiedendo di PHP; Mi aspetto che i principali ORM di PHP abbiano caratteristiche simili. Di solito, l'ORM è impostato su un caricamento lazy, ma puoi dirlo per caricare una determinata tabella quando esegui una query.

In effetti, è possibile caricare tutti i dati in una query. SQLAlchemy ha due modalità di caricamento ansiose: unita e sottoquery. unito fa tutto in un'unica query, che a volte è il modo più efficace, ma finisce con l'interrogazione di dati duplicati. carico subquery eager è proprio come il tuo approccio. Non sono sicuro che gli ORM PHP supportino questa distinzione.

    
risposta data 09.07.2014 - 09:46
fonte
4

L'inefficienza / il disordine deriva dall'idratare i dati troppo presto e troppo spesso. Con "idratazione" intendo istanziare gli oggetti dati da (quelli che presumo siano) record nel database.

Non è sempre necessario occuparsi di oggetti dati. Ad esempio, con questo codice ...

$posts = Posts::getPosts();

// get the ids into an array
$post_ids = array();
foreach($posts as $post) {
  array_push($post_ids, $post->id);
}

... Presumo che Posts::getPosts() abbia recuperato alcune righe dal database e creato un nuovo oggetto dati Post per ogni riga, solo per estrarre gli ID e gettare gli oggetti dati. Se hai aggiunto una funzione come Posts::getPostIds() che ha restituito una serie di numeri, puoi chiamarla al posto di quel blocco di codice.

$post_ids = Posts::getPostIds();

Il resto del tuo codice solleva una domanda più difficile. Idealmente vorrai comunque essere in grado di fare qualcosa come il tuo esempio originale, tranne per il fatto che vuoi che gli oggetti dati "commentati" vengano popolati da un recordset che avevi in precedenza piuttosto che eseguire una query separata per ogni post. Forse al posto di Comments::getComments , potresti aggiungere un metodo di istanza getComments a Post per gestirlo e passarlo a un recordset.

Tutto ciò presuppone che sia effettivamente necessario ottenere commenti per più post contemporaneamente; Non riesco a pensare a una situazione in cui avresti bisogno di farlo in modo casuale ma presumo tu sappia cosa stai facendo.

Per capire come gli altri framework gestiscono ORM, ti consiglio di dare un'occhiata alla documentazione di redbean per vedere come è usata, e se sei interessato, dai un'occhiata alla fonte.

    
risposta data 09.07.2014 - 08:03
fonte
0

Per la cronaca, qualsiasi ORM in qualsiasi stack tecnologico ha potenzialmente il problema N + 1. Anche il codice scritto a mano può avere questo problema.

Il modo più efficace per combattere il problema N + 1 è in realtà un JOIN in SQL. È qui che gli ingegneri del software devono assolutamente imparare l'SQL e come il loro codice di programmazione viene convertito in SQL.

Il codice originale che hai postato finisce con l'esecuzione di queste istruzioni SQL:

SELECT * FROM posts; # returns, let's say, posts 1-20

SELECT * FROM post_comments WHERE post_id = 1;
SELECT * FROM post_comments WHERE post_id = 2;
SELECT * FROM post_comments WHERE post_id = 3;
SELECT * FROM post_comments WHERE post_id = 4;
SELECT * FROM post_comments WHERE post_id = 5;
SELECT * FROM post_comments WHERE post_id = 6;
SELECT * FROM post_comments WHERE post_id = 7;
SELECT * FROM post_comments WHERE post_id = 8;
SELECT * FROM post_comments WHERE post_id = 9;
SELECT * FROM post_comments WHERE post_id = 10;
SELECT * FROM post_comments WHERE post_id = 11;
SELECT * FROM post_comments WHERE post_id = 12;
SELECT * FROM post_comments WHERE post_id = 13;
SELECT * FROM post_comments WHERE post_id = 14;
SELECT * FROM post_comments WHERE post_id = 15;
SELECT * FROM post_comments WHERE post_id = 16;
SELECT * FROM post_comments WHERE post_id = 17;
SELECT * FROM post_comments WHERE post_id = 18;
SELECT * FROM post_comments WHERE post_id = 19;
SELECT * FROM post_comments WHERE post_id = 20;

Questo crea un sacco di chatter di rete avanti e indietro.

Ciò che vuoi veramente è questo:

SELECT posts.*,
       comments.*
FROM posts
LEFT JOIN post_comments comments ON comments.post_id = posts.id;

Sì, ottieni "più" righe indietro del necessario, ma sono le chiacchiere di rete che provocano il collo della bottiglia. Qualunque buon ORM dovrebbe essere configurabile per farlo e gestirà in modo intelligente i record duplicati per ogni post e incollerà gli oggetti per te.

    
risposta data 04.06.2018 - 14:37
fonte
-2

Utilizzare l'approccio dell'usabilità della paginazione in base alla domanda. Non sovraccaricare, non sovraccaricare nulla: schermate, buffer, loop, flussi, file. Considera cosa e quanto ti serve in un momento di elaborazione e stabilisci la regola. Nessuna lista di schermate può presentare qualcosa di più grande dello schermo, nessun buffer di memoria può caricare una tabella di database completa, nessun socket può portare il trading azionario di un giorno in una sola volta, né sarà lì per servire milioni di richieste individuali. L'elaborazione ad alte prestazioni si basa sul microprocessing in batch

    
risposta data 03.06.2018 - 21:09
fonte

Leggi altre domande sui tag