どんなシステムでもトランザクションテーブルにコード値を持たせて、コード値の名称はマスタテーブルに持たせるという方式がスタンダードな方式になっていると思います。
その際にマスタテーブルをINNER JOINして名称を取得する方法と、SELECT句でサブクエリを使用してコード値の名称を取得する方法の2パターンがありますが、処理速度的にどちらが優れているか検証しました。
INNER JOINとサブクエリのコード値の名称取得SQL 処理速度検証
TB_SAMPLEに1,000万レコードが格納されており、カラム「ID」はPKとなっています。
MST_ITEMには10レコードが格納されており、カラム「ITEM_CD」はPKとなっています。
下記は前提条件としてのデータ件数とデータの内容になります・
前提条件
-------------------------------------------- -- トランザクションテーブルのレコード件数 -------------------------------------------- SELECT COUNT(*) FROM TB_SAMPLE; COUNT(*) ---------- 10000000 -------------------------------------------- -- コード値毎のレコード件数 -------------------------------------------- SELECT ITEM_CD ,COUNT(*) FROM TB_SAMPLE GROUP BY ITEM_CD ORDER BY ITEM_CD ; ITEM_CD COUNT(*) ---------- ---------- 1 998454 2 999044 3 1000188 4 1000279 5 1001524 6 999894 7 1000468 8 1000246 9 1000652 10 999251 -------------------------------------------- -- マスタテーブルの件数 -------------------------------------------- SELECT COUNT(*) FROM MST_ITEM; COUNT(*) ---------- 10 -------------------------------------------- -- マスタテーブルの内容 -------------------------------------------- SELECT ITEM_CD ,ITEM_NM FROM MST_ITEM ; ITEM_CD ITEM_NM ---------- -------------------- 1 歯ブラシ 2 ポテトチップス 3 アイスカフェラテ 4 牛丼 5 カレーライス 6 からあげくん 7 メロンパン 8 えんぴつ 9 筆箱 10 オレンジジュース
INNER JOINでコード値の名称を取得するSQL
SELECT Z.ITEM_NM ,COUNT(*) FROM ( SELECT X.ID ,Y.ITEM_NM FROM TB_SAMPLE X INNER JOIN MST_ITEM Y ON Y.ITEM_CD = X.ITEM_CD ) Z GROUP BY Z.ITEM_NM ;
サブクエリでコード値の名称を取得するSQL
SELECT Z.ITEM_NM ,COUNT(*) FROM ( SELECT X.ID ,( SELECT Y.ITEM_NM FROM MST_ITEM Y WHERE Y.ITEM_CD = X.ITEM_CD ) ITEM_NM FROM TB_SAMPLE X ) Z GROUP BY Z.ITEM_NM ;
INNER JOINとサブクエリの処理速度測定結果
回数/平均 | INNER JOIN | サブクエリ |
---|---|---|
1回目 | 9.01秒 | 11.58秒 |
2回目 | 8.68秒 | 11.88秒 |
3回目 | 8.40秒 | 11.40秒 |
平均 | 8.7秒 | 11.6秒 |
結論としては「INNER JOINでコード値の名称を取得するSQL」の方が処理速度的に優れています。
1,000万レコードで処理測定を行うと3秒ぐらいの違いがでます。
INNER JOINとサブクエリのコード値の名称取得SQL 実行計画 統計情報の比較
INNER JOINでコード値の名称を取得するSQL 実行計画
--------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 10 | 360 | 5570 (7)| 00:01:07 | | 1 | HASH GROUP BY | | 10 | 360 | 5570 (7)| 00:01:07 | | 2 | MERGE JOIN | | 10 | 360 | 5569 (7)| 00:01:07 | | 3 | TABLE ACCESS BY INDEX ROWID| MST_ITEM | 10 | 200 | 2 (0)| 00:00:01 | | 4 | INDEX FULL SCAN | SYS_C007813 | 10 | | 1 (0)| 00:00:01 | |* 5 | SORT JOIN | | 10 | 160 | 5567 (7)| 00:01:07 | | 6 | VIEW | VW_GBC_9 | 10 | 160 | 5566 (7)| 00:01:07 | | 7 | HASH GROUP BY | | 10 | 30 | 5566 (7)| 00:01:07 | | 8 | TABLE ACCESS FULL | TB_SAMPLE | 10M| 28M| 5274 (1)| 00:01:04 | ---------------------------------------------------------------------------------------------
サブクエリでコード値の名称を取得するSQL 実行計画
------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 10M| 209M| 5566 (7)| 00:01:07 | | 1 | TABLE ACCESS BY INDEX ROWID| MST_ITEM | 1 | 20 | 1 (0)| 00:00:01 | |* 2 | INDEX UNIQUE SCAN | SYS_C007813 | 1 | | 0 (0)| 00:00:01 | | 3 | HASH GROUP BY | | 10M| 209M| 5566 (7)| 00:01:07 | | 4 | VIEW | | 10M| 209M| 5274 (1)| 00:01:04 | | 5 | TABLE ACCESS FULL | TB_SAMPLE | 10M| 28M| 5274 (1)| 00:01:04 | -------------------------------------------------------------------------------------------
実行計画の違い
実行計画の違いは下記の3点になりました。
1. INNER JOINの方がCostが「4」多い
2. INNER JOINの方がBytesが「約208M」少ない
3. INNER JOINの方がマスターテーブルをFULLスキャンしているが、サブクエリの方はINDEXスキャンをしている
実行計画だけを確認するとINNER JOINのBytesが低く、サブクエリのBytesが極端に高いです。
サブクエリのクエリが遅くなる原因はBytesが高いという部分に起因していると読み取れます。
おそらくサブクエリは内部的にレコード件数分だけサブクエリを実行するため、ディスクI/Oがたくさん発生し遅くなるのではと思われます。
統計情報
統計項目 | INNER JOIN | サブクエリ |
---|---|---|
recursive calls | 1 | 0 |
db block gets | 0 | 0 |
consistent gets | 19282 | 19294 |
physical reads | 19277 | 19277 |
redo size | 0 | 0 |
bytes sent via SQL*Net to client | 928 | 928 |
bytes received via SQL*Net from client | 523 | 523 |
SQL*Net roundtrips to/from client | 2 | 2 |
sorts (memory) | 1 | 0 |
sorts (disk) | 0 | 0 |
rows processed | 10 | 10 |
統計項目の違いは下記になりました。
1. INNER JOINの方がrecursive calls(再帰的コールの回数)が「1」多い
2. INNER JOINの方がconsistent gets(一貫性読み込み回数)が「12」少ない
3. INNER JOINの方がsorts(memory)が「1」多い
統計情報の違いはほとんどありません。consistent gets(一貫性読み込み回数)だけ、INNER JOINの方が負荷は少しだけ少ないようです。
INNER JOINとサブクエリのコード値の名称取得SQL 処理速度計測の結論
サブクエリよりもINNER JOINを使った方が処理速度は速いので、なるべくINNER JOINを使った方がよいです。
INNER JOINを使った方が速いという理由は、INNER JOINはディスクI/Oが少なく、サブクエリは極端に多くなるためです。
実行計画のCostが高くてもディクスI/O(Bytes)が低いSQLの方が、処理速度が速いので、実行計画を確認する際はCostだけを確認しても失敗するので気を付けましょう。