データベースに SELECT 文を発行する際の db_select() と db_query() の使い分け方

こんにちは。 Drupal 8 での開発が一段落して Drupal 7 の世界に戻ってきた大野です。やっぱり慣れてると開発スピードが全然違いますね。

さて、今回はデータベースに SELECT 句を発行する際に使う db_query()db_select() の使い分けについてのお話です。 皆さんはどのように使い分けているのでしょうか。 Drupal 7 では PDO が採用され db_select() が使えるようになったので、問答無用でそれを使うべきなのかと思っていたのですが調べてみたところそうでもなさそうです。

db_querydb_select 関数ののおさらい

まずは簡単にそれぞれの関数について簡単におさらいしてみたいと思います。

db_query()

任意の SQL 文を直に実行できる関数です。 node テーブルから任意のコンテンツタイプの nid 値を取得する場合はこんな感じで書きますね。

$result = db_query("SELECT n.nid FROM {node} n WHERE n.type = :type ORDER BY n.nid", array(':type' => $type));

db_select()

SELECT 文専用の関数です。メソッドチェーンで SQL 文を組み立てていくことができます。上記の SQL 文をこの関数で作った場合以下の様なコードになります。

$result = db_select('node', 'n')
  ->fields('n', array('nid'))
  ->condition('type', $type)
  ->orderBy('nid')
  ->execute();

パフォーマンスの比較

db_select() 関数は db_query() 関数に比べると実行速度が遅いのはご存知でしょうか。今回は先人達が議論していた Compare db_query() and db_select() performance の内容を引用させていただくことにします。

この記事では geerlingguy さんがデータベースのパフォーマンスを計測するためのモジュールを作り、ベンチマークをして以下の結果が得られたと報告しています。

Results for 100 test runs
Array
(
    [db_query() Simple] => 0.431 ms
    [db_select() Simple] => 0.528 ms
    [db_query() with Joins] => 0.384 ms
    [db_select() with Joins] => 0.507 ms
)

確かに db_query() の方が早そうですね。ちなみにこのベンチマークのコードを見てみたところ単純に node テーブルから nid を取得するだけのものでした。

geerlingguy さんは以下の様にコメントしています。

  • シンプルなクエリーであれば db_query()db_select() より 22% 早い。
  • 2つ join したクエリーでは db_query()db_select() より 29% 早い。

計測用に作られたモジュールは Drupal Sandbox のページからダウンロードすることができますので、興味がある方はお試しください。

db_select() の様々な機能

パフォーマンスの観点だけで言うと db_query() 軍配が上がりましたが db_select() を使う理由はいくつかあります。 StackExchange に投稿されていた Given that db_select is much slower than db_query, why would I want to use it? の内容が秀逸でしたので意訳・抜粋して4つの理由を紹介します。

1. クエリーを動的にコンディションの設定をしたりジョインする場合

シンプルな SQL なら問題ありませんが、SQL 文をコードベースで組み立てていくと複雑な条件分岐だとうんざりするようなコードができあがってしまいますが、 db_select() のようにメソッドで SQL を組み立てていけばループ処理も容易ですし、比較的コードも見やすくなる利点があります。

db_select() を利用した SQL 文の組み立ては field_read_fields() が参考になります。

2. エクステンダーを利用する場合

例えば PagerDefaultTableSort などがあげられます。

node_page_default() 関数のコードを抜粋して見てみましょう。 extend() メソッドに PagerDefault の値がある点に注目してください。

$select = db_select('node', 'n')
  ->fields('n', array('nid', 'sticky', 'created'))
  ->condition('n.promote', 1)
  ->condition('n.status', 1)
  ->orderBy('n.sticky', 'DESC')
  ->orderBy('n.created', 'DESC')
  ->extend('PagerDefault')
  ->limit(variable_get('default_nodes_main', 10))
  ->addTag('node_access');

このクエリーを発行した後にレンダー配列へ以下のような内容を追加することで、これだけのコードでページャー機能を実装することができてしまいます。 LIMIT 句の組み立ては全て PagerDefault のエクステンダーが行ってくれます。

$build['pager'] = array('#theme' => 'pager');

3. 他のモジュールでクエリーを加工したい場合

db_select() にタグを追加することで自動的に該当する hook が呼び出されます。

特に node_access タグが便利なので紹介します。このタグを追加すると node_access() システムがアクセス権のあるノードのみ抽出されるようにクエリーを加工します。

以下の例では node_access と言うタグを追加するだけでアクセス権限があるノード ID のみを取得します。

$nids = db_select('node', 'n')
  ->fields('n', array('nid'))
  ->addTag('node_access')
  ->execute()
  ->fetchCol();

4. SQL 文の抽象化

例えばデータベースから取得した結果をランダムに並べ替えたい要件の場合、各ベンダー毎に書式が異なるためそれらに合わせて文を書き換える必要があるのですが、 db_select() では orderRandom() メソッドを利用することができます。

まとめ

特に機能的なことが必要の無い単純なクエリーであれば db_query() を使い、一方で Drupal の便利な API を使ったり動的な条件が絡む場合は db_select() を使うと言った具合にシチューエーションに応じて使い分けるのが一番のようですね。

コメントを追加

プレーンテキスト

  • HTMLタグは利用できません。
  • 行と段落は自動的に折り返されます。
  • ウェブページのアドレスとメールアドレスは自動的にリンクに変換されます。