はじめに
みなさんはmysql2を使ったことがありますか?Node.js上でMySQLに接続する際のクライアントライブラリとして、mysql2が主要な選択肢の一つだと思います。その際に、SQL文を実行するときのquery関数とexecute関数の使い分けが曖昧だったので整理してみました。
実行例
まず、下記の挙動の違いはなんでしょう?どちらのメソッドも、そのままSQL文を実行できます。また、プレースホルダ形式でクエリを実行すれば、SQLインジェクション対策も行われます。
// 引数や戻り値も同じで、違うのはqueryを使うかexecuteを使うかだけ
await connection.query(`SELECT * FROM table`);
await connection.query(`SELECT * FROM table WHERE userid = ?`, [userid]);
await connection.execute(`SELECT * FROM table`);
await connection.execute(`SELECT * FROM table WHERE userid = ?`, [userid]);
結論から言うと、query()とexecute()の違いとしてはSQL実行時に使われる通信プロトコルの違いです。MySQLでは、クライアントとサーバーがやり取りする際に「通信コマンド(プロトコル)」を使ってSQLを実行します。それぞれのメソッドがどのようなプロトコルを使用しているかによって、通信回数・安全性・パフォーマンスが変わってきます。以下の表に両者の特徴をまとめました。
特徴まとめ(query() と execute() の違い)
| 項目 | query() |
execute() |
|---|---|---|
| 内部通信プロトコル | COM_QUERY |
COM_STMT_PREPARE, COM_STMT_EXECUTE
|
| 通信回数 | 1 回(単発で完結) | 2 回(準備+実行) |
| SQL の送信方法 | SQL 文をそのまま送信 | SQL 構文と値を分離して送信(プリペアードステートメント) |
| プレースホルダの扱い | ライブラリが値を自動でエスケープ | 値をエスケープするのではなく、構文と値を分離するために利用される |
| SQL インジェクション対策 | クライアント側で防止(プレースホルダを使うとライブラリが自動でエスケープ) | サーバー側で防止(プリペアードステートメントの仕組みにより構文と値を分離) |
| キャッシュ | なし(毎回サーバー側でパース) | あり(パース結果を再利用可能) |
| 向いている場面 | 単発クエリ、DDL(CREATE、ALTER )などのプリペアードステートメント非対応構文 |
同一構文を繰り返し実行する処理、バルクインサート、安全性を重視するケース |
どう使い分けるべきか?
以上を踏まえると単純な SQL クエリであれば、1回の通信で完結するquery()を使用するほうが効率的です。
一方で、SQL インジェクション対策(プレースホルダを利用する構文) を行う場合は、サーバー側で安全に実行できるプリペアードステートメント方式のexecute()を選択するのが望ましいと言えるでしょう。これはIPAの記載でも「プリペアードステートメント方式のほうが原理的に安全」とされています(ソースはこちら)。
より細かく使い分けるには?
より適切に使い分けるためには、通信プロトコルの理解が重要になります。例えば、query()で使用されるCOM_QUERYは「単一の SQL 文しか実行できない」という制約があります。
一方、execute()が採用しているプリペアードステートメント方式では、COM_STMT_PREPAREによって構文がキャッシュされるため、同じ構文を繰り返し実行する場合は高速になります。ただし、構文が異なる場合はキャッシュが利用されず、COM_STMT_PREPAREとCOM_STMT_EXECUTEの2回の通信が発生します。そのため、そうしたケースでは1回の通信で完結するquery()を使った方が効率的という考え方もあります。
まとめ
基本的には、SQLインジェクションを防止する目的でプレースホルダを使用する場合はexecute()、使用しない場合はquery()を選択すればいいかなと思います。さらに細かく使い分けるには、それぞれの通信プロトコルの特徴を理解して使い分けるといいでしょう。