articles 詳細クエリの実行プラン(slug 指定)
対象: 本番 TiDB クラスタ (
blog_prd)調査日: 2026-06-30
関連: 一覧クエリの実行プラン
記事詳細ページ用の単一記事取得クエリ。slug と users.name の 2 つのユニークインデックスから Point_Get を 2 本走らせて HashJoin する形になっており、構造的にはこれ以上削れない。
対象クエリ
SELECT a.article_id, a.title, a.slug, a.user_id, a.content,
a.thumbnail, a.description, a.status, a.`type`,
a.published_at, a.created_at, a.updated_at
FROM articles a
JOIN users u ON a.user_id = u.user_id
WHERE a.status = 'published'
AND a.slug = '01f07hctzhjcwtdq4h6ew9stk8'
AND u.name = 'shuntaka';
実行プラン(合計 1.18ms / 1 行)
articles.slug と users.name の 2 本の Point_Get が HashJoin で合流する Y 字構造。
生 EXPLAIN ANALYZE 出力
id estRows estCost actRows task access object operator info memory disk
HashJoin_9 0.68 27876.31 1 root inner join, equal:[eq(articles.user_id, users.user_id)] 33.7 KB 0 Bytes
├─Point_Get_13(Build) 1.00 242.91 1 root table:users, index:uq_users_name(name)
└─Selection_12(Probe) 0.68 26010.67 1 root eq(articles.status, "published") 19.9 KB N/A
└─Point_Get_11 1.00 25960.77 1 root table:articles, index:uq_articles_slug(slug)
arguments: ("01f07hctzhjcwtdq4h6ew9stk8", "shuntaka")
主要 execution info(一部抜粋)
HashJoin_9: time:1.18ms, loops:2, build_hash_table:{total:804.7µs, fetch:799.9µs, build:4.83µs}, probe:{concurrency:5, total:5.01ms, max:1.02ms, probe:16.8µs, fetch and wait:4.99ms}Point_Get_13: time:763µs, Get:{num_rpc:4, total_time:1.62ms}, total_kv_read_wall_time:270.1µs, tikv_wall_time:341µsSelection_12: time:997.2µs, loops:2Point_Get_11: time:967.2µs, loops:3
サマリ表
番号は データフローの依存方向 で振っている。Probe 側の Point_Get_11(articles) → Selection_12 を #1–2、Build 側の Point_Get_13(users) を #3、合流地点の HashJoin_9 を #4 とした。
注意点:
EXPLAINテキストの並びはHashJoin_9 → Point_Get_13(Build) → Selection_12(Probe) → Point_Get_11の順で、本書の番号順とは一致しない「どっちが先に走るか」は EXPLAIN ANALYZE の time だけでは厳密には分からない(Build/Probe は部分的に並行する)。あくまで依存関係上の上流/下流の話
# |
Operator |
actRows |
time |
備考 |
|---|---|---|---|---|
1 |
|
1 |
0.97ms |
|
2 |
|
1 |
1.00ms |
|
3 |
|
1 |
0.76ms |
|
4 |
|
1 |
1.18ms |
|
所見
既に最適:
slugは UNIQUE なのでPoint_Get1 発で 1 行確定し、users.nameも同様。インデックスを追加して縮められる経路はない。Selectionは冗長気味だが許容:slugで 1 行確定した後にstatus='published'を再評価しているのは、下書き記事を公開 URL から弾くための仕様。actRows=1のままなので実コストは 30µs 程度で問題なし。HashJoinのオーバーヘッド: 1 行同士の結合にHashJoin(33.7KB)を使っているが、Build 側が 1 行なのでハッシュテーブル構築コストは無視できる。IndexJoinでも変わらない。user_id一致を結合で担保:articles.user_id = users.user_idのチェックを WHERE ではなく JOIN で行うことで、別ユーザーの slug 衝突(理論上は起きないが)に対しても安全。
一覧クエリ (2026-06-30) との比較
観点 |
詳細 (本書) |
一覧 (関連 survey) |
|---|---|---|
駆動経路 |
Point_Get × 2 |
Point_Get + IndexLookUp |
合計時間 |
1.18ms |
7.33ms |
返却行数 |
1 |
40 |
最重ステップ |
Point_Get (slug) |
TableRowIDScan + Selection |
改善余地 |
ほぼ無し |
複合インデックスで Sort 削減可 |
詳細クエリは現状で問題なし。改善ポーチは一覧側に注力する。