質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.11%

node.jsとMySQLでクエリを複数実行したい

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 7,883
退会済みユーザー

退会済みユーザー

node.js:v9.5.0
MySQL:5.7.21
MacOS:10.12

見よう見まねでこういうコードを書きました。

'use strict';

const mysql2 = require('mysql2/promise');

const pool = mysql2.createPool({
  host: 'localhost',
  user: 'foo',
  password: '',
  database: 'test'
});

const test = async () => {
  const job = async () => {
    const conn = await pool.getConnection();

    await conn.query('SELECT * FROM hoge_1', (err, rows, fields) => {
      if (err) throw err;
      console.log(rows);
    });

    await conn.query('SELECT * FROM hoge_2', (err, rows, fields) => {
      conn.release();
      if (err) throw err;
      console.log(rows);
    });
  };

  await job();
  await pool.end();
};

test();


やりたいことはnode.jsでMySQLを使ってデータを取得したいのですが、一つのプロセスでクエリを複数実行したいです。

async/awaitを使うことが条件となります。

期待していた結果は、job()内のawaitが上から順に実行されていき、クエリhoge_1、hoge_2の結果が落ちてくると思っていました。
ところがhoge_1の結果のみ返ってきて、pool.end()が効いていないせいか、処理を完了せず待機状態のままになってしまいます。

 
本当ならSQL文のWHERE以降の条件式に変数をもってきて、for文でループしたいのですが、うまくいかなくてバラバラにして試しています。

一度の処理でクエリを複数回すことは無理なのでしょうか?

 
追記:
ヒントを下さった皆様、ありがとうございました。

突然asyncモジュール禁止、async/awaitを使えと言われ、ドキュメントをきちんと把握せずに質問してしまい、申し訳ございませんでした。

ちょっとシンプルに変更した完成版のコードを貼っておきます。

'use strict';

const mysql = require('mysql2/promise');

const pool = mysql.createPool({
  host: 'localhost',
  user: 'foo',
  password: '',
  database: 'test'
});

const test = async () => {
  const conn = await pool.getConnection();

  const [rows_1, fields_1] = await conn.query('SELECT * FROM hoge_1');
  console.log(rows_1);

  const [rows_2, fields_2] = await conn.query('SELECT * FROM hoge_2');
  console.log(rows_2);

  const [rows_3, fields_3] = await conn.query('SELECT * FROM hoge_3');
  console.log(rows_3);

  await pool.end();
}

test();
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+5

手元で動かせないので、コードを見ての推測になってしまいますが

    await conn.query('SELECT * FROM hoge_1', (err, rows, fields) => {
      if (err) throw err;
      console.log(rows);
    });


なぜPromise(async/await)を利用しているのにコールバック関数を渡しているのでしょうか?
こちらを見ても、queryメソッドの引数はクエリとパラメータのみになっているようですが。

async/awaitを使用するのであれば

    const [rows, fields] = await conn.query('SELECT * FROM hoge_1');


が正しいかと思います(参考

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/21 00:41

    回答をありがとうございました。
    最初にドキュメントを見ておくべきでした、スミマセン。。。

    キャンセル

+2

awaitがどういう動きするのかをちゃんと覚えればすぐ解決する話だと思います。

コードを見た限り何にでもawait付けてますが、
awaitは非同期通信をよしなに処理してくれる魔法のロジックではありません。
awaitの特徴は下記です。

  • 右辺はPromise
  • resolveが実行されるまでひたすら遅延、resolve(value)を式の値として処理を継続する
  • rejectが実行されればreject(err)errを例外として投げ処理を抜ける

従って、awaitを使う場面ではawaitの右辺はPromiseであることが絶対条件です。
上記のコードはパット見いくつかPromiseを返さないように見えます。
なので、全てPromiseを返す関数を実行している事を確かめてください。

特にダメそうなのがk.tadaさんも仰ってるmysql2のqueryにコールバック関数乗せちゃってる箇所。
コールバックがない場合mysql2はPromiseを返す挙動になっているはずですが、
コールバック関数を指定した場合の挙動は私は把握してませんね…もし気になるならソースコードを実際に読んでみてください。


【おまけ】 完成版コードへの軽いレビュー

お疲れ様でした、ぐっと見やすい良いコードになりましたね。
折角なのでこの完成版コードを軽く見ていきます。

  • poolは直接queryメソッド叩いて使える
  • Promise.allと併用して並列処理っぽく
  • Promiseをリスト操作で生成する
  • asyncで作った関数はresolve, rejectを意識する

poolは直接queryメソッド叩いて使える

もう見たまんまです。
connを取り出しても良いですが、このまま使った方が楽ちんでしょう。

const test = async () => {
  const [rows_1, fields_1] = await pool.query('SELECT * FROM hoge_1');
  console.log(rows_1);

  const [rows_2, fields_2] = await pool.query('SELECT * FROM hoge_2');
  console.log(rows_2);

  const [rows_3, fields_3] = await pool.query('SELECT * FROM hoge_3');
  console.log(rows_3);

  await pool.end();
}

Promise.allと併用して並列処理っぽく

まずはこのコードをデベロッパーツールに突っ込んで結果を見て下さい。
戻り値がPromiseであることが確認出来ます。
(Numberの配列なんで即resolvedしてますが)

Promise.all([123]);
// Promise {<resolved>: Array(1)}

つまり、このように書けば並列処理になります。
これならばpoolでコネクションを複数本引っ張った意味も出来るでしょう。
(ちょっとこれを走らせて速度を確認してみてください。)

const test = async () => {
  const results = await Promise.all([
    pool.query('SELECT * FROM hoge_1'),
    pool.query('SELECT * FROM hoge_2'),
    pool.query('SELECT * FROM hoge_3')
  ]);
  console.log(results);

  await pool.end();
}

Promiseをリスト操作で生成する

pool.query何回も実行してますが、これはダサいです。
sqlの配列から動的に作るようにしましょう。

前項ではコードがキモい感じでしたが、
多少そのキモさが和らいでるのが確認できます。

const test = async () => {
  const sqls = [
    'SELECT * FROM hoge_1',
    'SELECT * FROM hoge_2',
    'SELECT * FROM hoge_3'
  ];
  const promises = sqls.map(sql => pool.query(sql));
  const results = await Promise.all(promises);
  console.log(results);

  await pool.end();
}

asyncで作った関数はresolve, rejectを意識する

これは実践とはかけ離れた条件下なので、既にやっていることかもしれませんが、
asyncはawaitの逆、つまり絶対にpromiseを返す関数として振る舞います。

  • returnで値を返せばresolve(value)と等価
  • throwで例外を投げればreject(err)と等価

特にtestの外でコネクションを張っておきながら、testの中で閉じるのもおかしな話です。
実践を見据えてその辺を解消していきます。

const mysql = require('mysql2/promise');

const pool = mysql.createPool({
  host: 'localhost',
  user: 'foo',
  password: '',
  database: 'test'
});

const test = pool => {
  const sqls = [
    'SELECT * FROM hoge_1',
    'SELECT * FROM hoge_2',
    'SELECT * FROM hoge_3'
  ];
  return Promises.all(sqls.map(sql => pool.query(sql)));
}

test(pool)
  .then(results => {
    console.log(results);
    pool.end();
  })
  .catch(err => {
    console.error(err);
    pool.end();
  });

リファクタリング1号としてはこんな感じになりました。
ついでにtest関数がPromise.allを直接返すのでasync指定が剥げちゃいましたね…

まぁ、関数ってのは値を入れたら結果を返すものなので、
このようにsql結果を受け取り、.then.catchを使って値を調査、poolを閉じた方が設計としては綺麗だと思います。
実践は色々また状況が違うので最適なコードも変わってくると思います、頑張ってください!

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/21 00:40

    回答をありがとうございました。参考になりました。

    キャンセル

  • 2018/03/21 19:08

    Promises.allの使い方がよくわかりました。
    ありがとうございます! とても参考になりました。

    キャンセル

checkベストアンサー

+1

こちらを参考にまずは書き直してみてはいかがでしょう?

 ES7 Async Await - Promise wrappers / node-mysql2

https://github.com/sidorares/node-mysql2/blob/master/documentation/Promise-Wrapper.md#es7-async-await

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/21 00:39

    回答をありがとうございました。

    Promise.allは便利そうですね。
    ただselect sleep(2)、select sleep(3)とあるところにSELECT * FROM hoge文を入れてみたのですが、並列処理のせいか確認することができませんでした。。。

    また必要になった時に試してみます、ありがとうございます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.11%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る