基本
SQLは2種類に分けられる。
検索か、検索でないかだ。
パフォーマンスについて気にするのは、ほぼ検索のときだけ。
数百件のデータから検索するときには問題にならないSQLも、数万、数十万とデータ量が増えると重すぎて動かなくなる。
データ量が増えても、検索の負荷はそれほど増えないSQLにすることが必要。
そのためには、インデックスを使うことがポイントとなる。
インデックスというのは、図書館の分類法のような仕組みで、データを木構造に順番どおりに並べて、データの位置と検索速度が比例しないようにできる。
50番目だろうが、5千兆番目だろうが、同じくらいの速度で検索できるようになる。
(もちろん、全体のデータ量が増えれば検索時間も増える。対数的に。でも、B+木の構造であれば枝の深さが等しい、平衡木なので、何番目かには比例しない)
気をつけること
group by, sort by
これらをインデックスの貼られてないカラムに対して行うと、重くなる原因になる。
JOIN
JOINの結合条件にインデックスが貼られていないカラムを使用すると、重くなる原因になる。
また、一般的に、対象のレコード数が小さいテーブルを先に結合するほうが、検索対象となるレコード数を節約できる。
後方一致検索
後方一致ならインデックスが使えるが、前方一致だと使えないので注意。
関数を使用する
検索値のほうに関数を使用するならいいが、カラムのほうに使用してしまうとインデックスが効かない。
UNION
unionはソートして重複行を削除してくれる。
その分パフォーマンスが悪いことがある。
重複行を取得してもよければ、union allを使用しよう。
N+1問題
関連テーブルからデータを取得するようなときに、SQLがN+1回発行される有名な問題。
1回分は主テーブルからN個のリストを取得するとき。
N回分は、関連テーブルのデータを、主テーブルのレコード数分繰り返して取得するとき。
Joinで一度に取得することが望ましい。
(とはいえ場合によっては適宜もってきたほうがいいこともあるし、ORMではLazy Loadといってあえて一度に持って来ない設定もできることが多い。)
サブクエリー
サブクエリーを使う場合は、JOINで使用することが多いと思う。
その場合、できるだけサブクエリー内で取得される件数を減らしたほうがいい。
例)
SELECT * FROM A INNER JOIN (SELECT id,created_at FROM B) as sub ON A.b_id=sub.id WHERE b.created_at > '2022-10-10'
よりも
SELECT * FROM A INNER JOIN (SELECT id FROM B WHERE b.created_at > '2022-10-10') as sub ON A.b_id=sub.id WHERE b.created_at > '2022-10-10'
のほうがいい。
まとめ
インデックスには、メモリーを使うとか、登録処理のたびに作り直すオーバヘッドが生じるなど、デメリットもあるので、要らないなら作らなくていい。
また、DBがどのインデックスを使用するかは、統計的に決まったりもするので、パフォーマンスは理論上だけでは決まらない。
実際に測ってみたり、実行計画をみたりするのが大事。