「JOINはDBサーバの負荷が高くなる」は本当か?
「JOINは複雑なので、単純なSQLに分割してぐるぐる系で取得すれば、処理が遅くなったとしても、DBサーバの負荷は減る」と考えているエンジニアが実際に存在します。前回解説したとおり、SQLのオーバーヘッドの大きさをイメージできれば、「そんなことはない」と理解できたかもしれませんが、さらに深く理解するために、本稿ではJOINを分割したときと、JOINしたときの違いを見てみましょう。
JOINした(一発系の)リスト1を手続き型言語に直してみると、リスト2になります。
ぐるぐる系のリスト3を手続き型言語に直してみると、リスト4になります。
見比べてみると、「ぐるぐる系」でも、DBサーバの処理は何ひとつ減っていないことがわかります。
第4回の図2と第3回の「DBエンジンがSQL実行前に行う内部処理」をもう一度見てください。ぐるぐる系は、この膨大なSQLのオーバーヘッドとネットワークの処理が無駄に繰り返されるため、DBサーバの負荷を下げるどころか、負荷を大幅に上げることになるのです。
APサーバで肩代わりできる処理
APサーバで処理することによってDBサーバの負荷が下がるということは、APサーバがDBサーバの処理を肩代わりしているということになります。APサーバで肩代わりできる処理は次のものしかありません。
- マスタ類のキャッシュ → APサーバの同期が取れれば有効
- SELECT句の処理 → SELECT句でソートするWindow関数以外は誤差
- ループのブレイク処理 → 誤差
- ソート処理 → DBサーバの負荷を下げるには有効
DBサーバの負荷をどうしても下げたいのであれば、SQLでのORDER BYを禁止にすべきですが、ほとんどの共通仕様やコーディングルールで、「ORDER BYを必ず付けること」となっています。ORDER BY(ソート)は「1ページにつき10件ずつ表示する」といった表示件数を制御するページングが必要なWebシステムでは意味がありますが、そうでなければAPサーバでソートしても問題はありません。ソートは非常に重い処理ですので、シングルポイントになるDBサーバで処理するより、分散できるAPサーバで処理するほうが良いでしょう。
SQLでどこまで処理すべきか
C言語やアセンブリ言語の経験がないエンジニアが増えています。そのため、CPUとメモリの関係をあまり意識することがなくなっています。ここでは、CPUやメモリを意識してSQLについて考えてみましょう。
SQLはRDBMSのインプロセスで動作します。インプロセスとはRDBMSのメモリ空間内で処理されるという意味で、ポインタ(メモリのどこにデータがあるかを示すもの)で処理できます。C言語(C++)の経験がなければ「参照渡しできる」と読み替えても良いでしょう。APサーバは別のメモリ空間で動作しています。APサーバから見れば、RDBMSのメモリ空間のデータを直接読み書きすることはできませんから、お互いに値渡しするしかありません。
ここで第1回の図1で解説したページ単位でのI/Oを思い出してください。インプロセスであればポインタ(参照渡し)で処理できる内容でも、APサーバで処理させるためにはDBサーバはデータをバッファからワークメモリにコピーして、レコード、フィールドに加工しなければなりません。つまり、「できる限りSQLで処理するほうがDBサーバの負荷が下がる」というのは、「DBサーバから大量のデータをAPサーバにすべて値渡しして処理するより、DBサーバ内でできる処理は参照渡しで処理するほうが効率が良い」と考えれば理解しやすいのではないでしょうか。SQLのオーバーヘッドの繰り返しを避けるだけでなく、内部処理もSQLのほうが効率的になるわけです。
設計時に「SQLでどこまで処理するか」を決めるには、処理全体を自分が得意な手続き型言語で設計し、「どのメモリ空間で処理すべきか、ネットワークを越えて値渡しで処理することで効率的になる処理は何か?」と切り分けて考えてみましょう。そうすると、APサーバが肩代わりできる処理は前述の4つしかないことが理解できるはずです。ですから、ほとんどの場合、「SQLでどこまで処理すべきか」というのは、「ソート以外のデータに関連する処理はすべてSQLで処理すべき」となります。
「そんな極端な」という声が聞こえそうですが、DBサーバで利用される言語がSQLではなくAPサーバと同じ言語であったなら、「APサーバで処理すべき」と考えるエンジニアは少ないはずです。SQLになったときに逆の判断になる理由は、合理的に判断した結果でしょうか? 「SQLが嫌い(わかりにくい)」という主観が影響してはいませんか?