Rolf B: Verständnisproblem: MySQL-Queries in Node.js

Beitrag lesen

Hallo Klaus1,

der zeitliche Ablauf ist so, dass in der While-Schleife die getConnection-Aufrufe laufen. Diese erfolgen asynchron, richtig, die entsprechenden Aufrufe gehen an den SQL Treiber und die Promises registrieren sich intern auf entsprechende Callbacks. Da Du die Queries jeweils in einem eigenen then-Handler durchführst, sind sie säuberlich serialisiert.

Nun ist es wichtig zu wissen, dass Node.js auf Google V8 aufbaut und darum wohl genau so single-threaded ist. D.h. eine Verarbeitung der Callbacks beginnt erst, wenn das Hauptprogramm fertig ist.

Deine Callbacks warten also, bis die Schleife fertig ist. Wenn Du i in einem Callback erhöhst, wird es erst erhöht, wenn die Schleife fertig ist. Die wird aber nicht fertig. Weil das Erhöhen von i auf das Ende der Schleife wartet. Deswegen forderst Du neue Connections an, bis der Heap platzt.

Wenn Du i außerhalb der then-Handler erhöhst, hast Du ein anderes Problem. WEIL die Callbacks erst loslaufen, wenn die Schleife zu Ende ist, findet jeder von ihnen i=6 oder i=1001 vor (je nach Schleifengrenze). Und die connection-Variable wird kreuz und quer überschrieben. Brrr.

Wenn Du Callbacks in einer Schleife registrieren willst, musst Du die Aktivitäten jedes Schleifendurchlaufs in eine Funktion packen und alles, was pro Durchlauf individuell sein muss, als Parameter übergeben bzw. als lokale Variable der Funktion deklarieren, damit der Datenstand, der für diese Schleife gebraucht wird, in einer Closure gekapselt ist.

for (var i=0; i<100; i++) {
   collectValues(i);
}

function collectValues(i) {
   // Diese Variable liegt in der Closure der Callbacks und ist daher für jedes 
   // i einzigartig. Auch i liegt - als Parameter der Funktion - in der Closure
   var connection;

   pool.getConnection()
   .then(function(conn) {
      connection = conn;
      return connection.query("...", param1);
   })
   .then(function(rows) {
      console.log(i + "-1. "+rows[0].feldtyp);
      return connection.query("...", param2);
   })
   .then(...)
   .then(...)
   .then(function(rows) {
      console.log(i + "-4. "+rows[0].feldtyp);
      connection.release();
   });
}

Wenn Du es so machst, würde ich vermuten, dass Du das erwartete Durcheinander der Ergebnisse verschiedener Durchläufe in der Ausgabe bekommst.

Vom Experiment mal weggeguckt: Für den gezeigten Anwendungsfall wäre es wohl einfacher, die Query anders aufzusetzen, so dass alle benötigten Werte in einem Ergebnis geliefert werden (z.B. SELECT idnr, feldtyp FROM werte WHERE idnr in (?, ?, ?, ?) ), dann bekommst Du 4 Rows mit idnr und Feldtyp. Dann hast Du weniger Promises, weniger Serialisierungsaufwand und auch weniger Anfragen an den SQL Server.

Noch was anderes. Du KÖNNTEST die 4 Queries parallel starten, dann bekommst Du von jedem query-Aufruf ein eigenes Promise, und über Promise.all kannst Du ein Sammel-Promise erzeugen, das auf alle Ergebnisse wartet.

pool.getConnection()
.then(function(conn) {
   // Erzeuge Array (iterable-Objekt) mit 5 Einträgen. Die Connection auf Position 0,
   // die Promises für die Query-Ergebnisse auf den Positionen 1-4.
   var queries = [
      conn,
      conn.query("SELECT ...", param1),
      conn.query("SELECT ...", param2),
      conn.query("SELECT ...", param3),
      conn.query("SELECT ...", param4)
   ];
   // Erzeuge ein Sammelpromise, das auf alle 4 Queries wartet.
   return Promise.all(queries);
})
.then(function(results) {
   results[0].release();    // Connection freigeben
   console.log("1. " + results[1][0].feldtyp);
   console.log("2. " + results[2][0].feldtyp);
   console.log("3. " + results[3][0].feldtyp);
   console.log("4. " + results[4][0].feldtyp);
   
})

Das Interessante ist, dass der Parameter von Promise.all nicht nur aus Promises bestehen muss. Es sind auch non-Promise Werte erlaubt, die werden dann 1:1 durchgereicht. Auf diese Weise kannst Du das conn-Objekt durchreichen und brauchst keine Variable dafür.

Alle JavaScript-Beispiele ungetestet und auf eigene Gefahr!

Rolf

--
sumpsi - posui - clusi