|
| 1 | +{ |
| 2 | + "cells": [ |
| 3 | + { |
| 4 | + "cell_type": "markdown", |
| 5 | + "id": "015e0603", |
| 6 | + "metadata": {}, |
| 7 | + "source": [ |
| 8 | + "# MySQL 8.0.40\n", |
| 9 | + "\n", |
| 10 | + "## 0) 前提\n", |
| 11 | + "\n", |
| 12 | + "* エンジン: **MySQL 8**\n", |
| 13 | + "* 並び順: 任意(`ORDER BY` なし)\n", |
| 14 | + "* `NOT IN` は未使用\n", |
| 15 | + "* 判定は **ID 基準**、表示は仕様どおり `actor_id, director_id`\n", |
| 16 | + "\n", |
| 17 | + "## 1) 問題\n", |
| 18 | + "\n", |
| 19 | + "* `ある俳優 (actor_id) と監督 (director_id) の組み合わせで、協働回数が3回以上のペアを求める。`\n", |
| 20 | + "* 入力テーブル例: `ActorDirector(actor_id INT, director_id INT, timestamp INT PRIMARY KEY)`\n", |
| 21 | + "* 出力仕様: `actor_id, director_id`(重複なし・順不同)\n", |
| 22 | + "\n", |
| 23 | + "---\n", |
| 24 | + "\n", |
| 25 | + "## 2) 最適解(単一クエリ)\n", |
| 26 | + "\n", |
| 27 | + "> ウィンドウ集計でペアごとの協働回数を数え、3回以上のみを射影。重複除去は `DISTINCT`。\n", |
| 28 | + "\n", |
| 29 | + "```sql\n", |
| 30 | + "WITH win AS (\n", |
| 31 | + " SELECT\n", |
| 32 | + " actor_id,\n", |
| 33 | + " director_id,\n", |
| 34 | + " COUNT(*) OVER (PARTITION BY actor_id, director_id) AS coop_cnt\n", |
| 35 | + " FROM ActorDirector\n", |
| 36 | + ")\n", |
| 37 | + "SELECT DISTINCT\n", |
| 38 | + " actor_id,\n", |
| 39 | + " director_id\n", |
| 40 | + "FROM win\n", |
| 41 | + "WHERE coop_cnt >= 3;\n", |
| 42 | + "\n", |
| 43 | + "Runtime 349 ms\n", |
| 44 | + "Beats 56.27%\n", |
| 45 | + "\n", |
| 46 | + "```\n", |
| 47 | + "\n", |
| 48 | + "* ポイント: `COUNT(*) OVER (PARTITION BY actor_id, director_id)` でペアごとの総件数を1パスで算出。\n", |
| 49 | + "* 出力は仕様列のみ、順序指定なし。\n", |
| 50 | + "\n", |
| 51 | + "---\n", |
| 52 | + "\n", |
| 53 | + "## 3) 代替解\n", |
| 54 | + "\n", |
| 55 | + "> 集約で十分に速いケース。実務ではこちらが最小コストになりやすい。\n", |
| 56 | + "\n", |
| 57 | + "```sql\n", |
| 58 | + "SELECT\n", |
| 59 | + " actor_id,\n", |
| 60 | + " director_id\n", |
| 61 | + "FROM ActorDirector\n", |
| 62 | + "GROUP BY actor_id, director_id\n", |
| 63 | + "HAVING COUNT(*) >= 3;\n", |
| 64 | + "\n", |
| 65 | + "Runtime 348 ms\n", |
| 66 | + "Beats 57.64%\n", |
| 67 | + "\n", |
| 68 | + "```\n", |
| 69 | + "\n", |
| 70 | + "* 追加の手段(参考・必要時のみ): 事前にユニークな重複を除く必要があれば `SELECT DISTINCT actor_id, director_id, timestamp ...` の下位派生を作り `GROUP BY`。\n", |
| 71 | + "\n", |
| 72 | + "---\n", |
| 73 | + "\n", |
| 74 | + "## 4) 要点解説\n", |
| 75 | + "\n", |
| 76 | + "* **方針**: ペア単位で出現回数をカウント → しきい値(3回)でフィルタ → 必要列のみ投影。\n", |
| 77 | + "* **NULL/重複**: 入力列はINTでNULL前提なし、`timestamp` は主キーで重複なし。\n", |
| 78 | + "* **インデックス**: `PRIMARY KEY(timestamp)` だけだとペア集計で全表走査になりがち。\n", |
| 79 | + "\n", |
| 80 | + " * 可能なら **複合インデックス `(actor_id, director_id)`** を作成すると `GROUP BY` / `PARTITION BY` の集約が効率化。\n", |
| 81 | + "\n", |
| 82 | + "---\n", |
| 83 | + "\n", |
| 84 | + "## 5) 計算量(概算)\n", |
| 85 | + "\n", |
| 86 | + "* ウィンドウ解: 各パーティション集計で **O(N)**〜**O(N log N)**(実装・メモリアルゴ次第)。\n", |
| 87 | + "* `GROUP BY` 解: ソート/ハッシュ集約で **O(N log N)** 近辺(適切なインデックスで実効はほぼ **O(N)**)。\n", |
| 88 | + "\n", |
| 89 | + "---\n", |
| 90 | + "\n", |
| 91 | + "## 6) 図解(Mermaid 超保守版)\n", |
| 92 | + "\n", |
| 93 | + "```mermaid\n", |
| 94 | + "flowchart TD\n", |
| 95 | + " A[入力 テーブル]\n", |
| 96 | + " B[ペア単位の回数を算出]\n", |
| 97 | + " C[回数が3以上を抽出]\n", |
| 98 | + " D[出力 俳優ID 監督ID]\n", |
| 99 | + " A --> B\n", |
| 100 | + " B --> C\n", |
| 101 | + " C --> D\n", |
| 102 | + "```\n", |
| 103 | + "\n", |
| 104 | + "---\n", |
| 105 | + "\n", |
| 106 | + "### 補足(実運用メモ)\n", |
| 107 | + "\n", |
| 108 | + "* データ量が多い場合は `GROUP BY` 案+ `(actor_id, director_id)` の複合インデックスが最も簡潔で速い構成になりやすいです。\n", |
| 109 | + "* 結果順は任意のため、**`ORDER BY` を付けない**ことで不要なソートを省きます。\n", |
| 110 | + "\n", |
| 111 | + "結論:**クエリ自体は最短経路**です。差が出るのは **実装より実行計画(インデックス・統計・メモリ)**。以下を順に打つと体感で大きく縮みます。\n", |
| 112 | + "\n", |
| 113 | + "---\n", |
| 114 | + "\n", |
| 115 | + "## 即効性のある改善(順に適用)\n", |
| 116 | + "\n", |
| 117 | + "1. **複合インデックスを追加(最重要)**\n", |
| 118 | + " `GROUP BY actor_id, director_id` をインデックス順でなぞらせ、**ソート/テンポラリ回避**を狙います。\n", |
| 119 | + "\n", |
| 120 | + "```sql\n", |
| 121 | + "CREATE INDEX idx_actor_director ON ActorDirector (actor_id, director_id);\n", |
| 122 | + "```\n", |
| 123 | + "\n", |
| 124 | + "* これで `GROUP BY` 案はほぼ **インデックス順走査→集約** に変わります。\n", |
| 125 | + "* 結果列がキーだけなので、**セカンダリインデックスのみ**で完結 (InnoDB はセカンダリ葉にPK含むが今回未参照)。\n", |
| 126 | + "\n", |
| 127 | + "2. **ウィンドウ版は封印、`GROUP BY` を採用**\n", |
| 128 | + " ウィンドウ版は `COUNT OVER` → `DISTINCT` で無駄に行を増やします。最速はこれ:\n", |
| 129 | + "\n", |
| 130 | + "```sql\n", |
| 131 | + "SELECT actor_id, director_id\n", |
| 132 | + "FROM ActorDirector\n", |
| 133 | + "GROUP BY actor_id, director_id\n", |
| 134 | + "HAVING COUNT(*) >= 3;\n", |
| 135 | + "```\n", |
| 136 | + "\n", |
| 137 | + "> インデックスが効けば **ファイルソート/テンポラリなし** で流せます。\n", |
| 138 | + "\n", |
| 139 | + "3. **統計の鮮度を上げる**\n", |
| 140 | + "\n", |
| 141 | + "```sql\n", |
| 142 | + "ANALYZE TABLE ActorDirector;\n", |
| 143 | + "```\n", |
| 144 | + "\n", |
| 145 | + "* 古い統計だとインデックスを握ってくれないことがあります。\n", |
| 146 | + "\n", |
| 147 | + "4. **ハッシュ集約の挙動を確認(MySQL 8)**\n", |
| 148 | + " 場合によってはハッシュ集約が一時領域を使い遅くなることがあります。悪化時のみヒントで切替。\n", |
| 149 | + "\n", |
| 150 | + "```sql\n", |
| 151 | + "-- 悪い計画が出たときだけ\n", |
| 152 | + "SELECT /*+ NO_HASH_AGGREGATION() */\n", |
| 153 | + " actor_id, director_id\n", |
| 154 | + "FROM ActorDirector\n", |
| 155 | + "GROUP BY actor_id, director_id\n", |
| 156 | + "HAVING COUNT(*) >= 3;\n", |
| 157 | + "```\n", |
| 158 | + "\n", |
| 159 | + "---\n", |
| 160 | + "\n", |
| 161 | + "## 追加の選択肢(データ量・更新頻度しだい)\n", |
| 162 | + "\n", |
| 163 | + "* **集計サマリ表**(マテビュー代替)\n", |
| 164 | + "\n", |
| 165 | + " ```sql\n", |
| 166 | + " -- 初期ロード\n", |
| 167 | + " CREATE TABLE CoopSummary AS\n", |
| 168 | + " SELECT actor_id, director_id, COUNT(*) AS coop_cnt\n", |
| 169 | + " FROM ActorDirector\n", |
| 170 | + " GROUP BY actor_id, director_id;\n", |
| 171 | + "\n", |
| 172 | + " CREATE UNIQUE INDEX ux_coop ON CoopSummary(actor_id, director_id);\n", |
| 173 | + " ```\n", |
| 174 | + "\n", |
| 175 | + " * 以降はバッチで増分反映(新規 `ActorDirector` のみ集計→`INSERT ... ON DUPLICATE KEY UPDATE`)。\n", |
| 176 | + " * 本問は「閾値3以上の存在判定」なので `WHERE coop_cnt >= 3` で**即時応答**。\n", |
| 177 | + "\n", |
| 178 | + "---\n", |
| 179 | + "\n", |
| 180 | + "## 期待できる効果(目安)\n", |
| 181 | + "\n", |
| 182 | + "* 複合インデックス導入だけで、**中〜大規模**でも 2〜10倍短縮が珍しくありません。\n", |
| 183 | + "* あなたの計測(~350ms)規模なら、**2桁〜100ms台**まで落ちる可能性が高いです(I/O・メモリ次第)。\n", |
| 184 | + "\n", |
| 185 | + "---\n", |
| 186 | + "\n", |
| 187 | + "## チェックリスト(必ず EXPLAIN)\n", |
| 188 | + "\n", |
| 189 | + "```sql\n", |
| 190 | + "EXPLAIN\n", |
| 191 | + "SELECT actor_id, director_id\n", |
| 192 | + "FROM ActorDirector\n", |
| 193 | + "GROUP BY actor_id, director_id\n", |
| 194 | + "HAVING COUNT(*) >= 3;\n", |
| 195 | + "```\n", |
| 196 | + "\n", |
| 197 | + "* `key = idx_actor_director` が選ばれているか\n", |
| 198 | + "* `Extra` に **Using index**、**Using temporary/Using filesort が消えているか**\n", |
| 199 | + "* 行数見積り(rows)が現実的か(統計が効いているか)\n", |
| 200 | + "\n", |
| 201 | + "---\n", |
| 202 | + "\n", |
| 203 | + "## まとめ(処方箋)\n", |
| 204 | + "\n", |
| 205 | + "* **ベースクエリ**:`GROUP BY ... HAVING COUNT(*) >= 3`\n", |
| 206 | + "* **必須インデックス**:`(actor_id, director_id)`\n", |
| 207 | + "* **統計更新**:`ANALYZE TABLE`\n", |
| 208 | + "* (必要時のみ)**NO_HASH_AGGREGATION ヒント**\n", |
| 209 | + "* 本番ワークロードでまだ重いなら、**集計サマリ表**で根本対策\n", |
| 210 | + "\n", |
| 211 | + "この順で手当すれば、数字はまだ縮みます。ボトルネックはクエリじゃなく**計画**—です。\n" |
| 212 | + ] |
| 213 | + } |
| 214 | + ], |
| 215 | + "metadata": { |
| 216 | + "language_info": { |
| 217 | + "name": "python" |
| 218 | + } |
| 219 | + }, |
| 220 | + "nbformat": 4, |
| 221 | + "nbformat_minor": 5 |
| 222 | +} |
0 commit comments