JOINが増えるとパフォーマンスが低下します。
INNER JOINの場合は論理和になるので、A、B、C、Dと4つのテーブルがあった場合は、AとB、AとC、AとD、BとC、BとD、CとDというように重なりを調べていくので指数関数的に増加することになります。
JOINのテーブルスキャンは掛け算なので、10行と10行なら100行、1000行と1000行なら1000000行のスキャンが走ります。
じゃあ、JOINを使ってはいけないのか?
それはないです。N+1問題の温床になってしまったりするので使わないという選択肢はないです。
対策
インデックスを貼る。
100行と100行のスキャンの場合、インデックスが貼られてなかったら100✖️100の10000行のスキャンが走りますが、インデックスが貼れていたら、100+100の200行のスキャンで済みます。
SQLを見直す。
JOINの回数を減らすこと。
そもそも、不要なJOINをしていないか見直す。
WHERE句の前にON句でできるだけ絞り込むこと。
ON句で事前に絞り込めるのであれば、JOIN元やJOIN先のテーブルの行数が少なくなっているほどJOINした際のパフォーマンスは向上します。何ならWHERE句で使っている条件は全てサブクエリに持っていき、JOIN元やJOIN先の行数を減らすことに活用することが懸命です。なお、ON句は結合条件のみに使うと思われている方も多いのですが、普通に条件の絞り込みにも使えるのでそれがSQLの良い実装方法にもなりますので覚えておくと良いです。
OracleとMySQLがらみのロック問題は理解しておく
OracleとMySQLでは「複数のテーブルをJOINしてそのうちの一つをアップデートする処理を行う場合MySQLの方がOracleよりロックの範囲が広い」という問題が発生します。
Oracleでは以下のようにJOINするSQLのFOR UPDATEの後ろに「テーブル名.カラム名」と指定することでロックの範囲を狭くすることができますが、MySQLだとできないのです。
1 2 3 4 5 |
SELECT * FROM テーブル1 INNER JOIN テーブル2 ON テーブル1.カラム1 = テーブル2.カラム1 where テーブル1.カラム1 = ? FOR UPDATE |
なのでMySQLでは以下のようなサブクエリによる対処を取ります。
1 2 3 4 |
SELECT * FROM (SELECT * FROM テーブル1 where カラム1 = ? FOR UPDATE) a INNER JOIN テーブル2 ON テーブル1.カラム1 = テーブル2.カラム1 |
こうすることでMySQLでもFOR UPDATEの範囲がテーブル1だけになります。こうすることで特にパフォーマンスが劣化することなくロックの範囲を限定できます。
この記事へのコメントはありません。