1. 从一次读书笔记卡壳说起:哈希连接到底访问了几次表
我在整理 Oracle 读书笔记时,卡在一个很具体的问题上:哈希连接(Hash Join)里,驱动表和被驱动表到底各访问几次?书上写的是「0 次或 1 次」,但光看结论记不住,过两天就忘。真正让我记住的,是把执行计划里的 Starts、A-Rows、Buffers 这几列对着实验数据一行行读下来。
这篇笔记复盘的就是这个过程:先用三组 SQL 实验把哈希连接的表访问次数验证清楚,再把整理好的笔记通过 TaoToken 统一 Key/API 通道接进 AI 工具,让后续检索和追问不用反复翻文档。如果你也在做 Oracle 执行计划相关的读书笔记,或者想把技术笔记接进 AI 助手做长期检索,这套流程可以直接跟做。
核心检索词先摆出来:Oracle 哈希连接是什么?它是优化器在大表关联时常用的一种连接方式,把较小的行源在内存里建成哈希表,再用另一侧的行去探测匹配。适合谁?适合正在读 Oracle 优化类书籍、需要把执行计划读透的开发和 DBA。能做什么?能让你从「背结论」变成「看数据说话」。
我试过把执行计划截图丢给 AI 让它解释,结果它把 Starts 和 A-Rows 混着讲,越看越乱。后来改成先把实验数据整理成结构化笔记,再通过统一通道喂给模型,追问质量明显不一样。下面按「实验验证 → 配置接入 → 验证请求 → 排错」的顺序展开。
2. 哈希连接执行计划精读:三组实验的数据对照
2.1 基线实验:两侧各访问 1 次
先看最基础的关联查询,用 leading 和 use_hash 提示固定执行路径:
SELECT /*+leading(t1) use_hash(t2)*/ * FROM t1, t2 WHERE t1.id = t2.id;执行计划里关键几行是这样的:
| Id | Operation | Name | Starts | E-Rows | A-Rows | Buffers | Used-Mem |
|---|---|---|---|---|---|---|---|
| 0 | SELECT STATEMENT | 1 | 100 | 1018 | |||
| 1 | HASH JOIN | 1 | 100 | 100 | 1018 | 1235K | |
| 2 | TABLE ACCESS FULL | T1 | 1 | 100 | 100 | 7 | |
| 3 | TABLE ACCESS FULL | T2 | 1 | 118K | 100K | 1011 |
读法要点:Starts 列表示该操作实际执行了几次。这里 T1 和 T2 的 Starts 都是 1,说明两侧各被访问一次。Buffers 列能看出代价分布——T2 扫了 1011 个块,T1 只有 7 个块,因为 T1 是小表,被选作驱动侧建哈希表,T2 作为探测侧。Used-Mem 显示哈希表实际用了约 1235K 内存。
注意:E-Rows 是优化器估算行数,A-Rows 是实际返回行数。两者差距大时,往往意味着统计信息需要更新,这会直接影响连接方式的选择。
2.2 实验一:驱动侧过滤后归零,被驱动侧访问 0 次
给驱动表 T1 加一个几乎不可能命中的过滤条件:
SELECT /*+leading(t1) use_hash(t2)*/ * FROM t1, t2 WHERE t1.id = t2.id AND t1.n = 999999999;执行计划变化很关键:
| Id | Operation | Name | Starts | E-Rows | A-Rows | Buffers |
|---|---|---|---|---|---|---|
| 1 | HASH JOIN | 1 | 1 | 0 | 7 | |
| 2 | TABLE ACCESS FULL | T1 | 1 | 1 | 0 | 7 |
| 3 | TABLE ACCESS FULL | T2 | 0 | 118K | 0 | 0 |
T1 的 Starts 是 1,但 A-Rows 是 0——它被访问了一次,只是没返回任何行。T2 的 Starts 直接是 0,Buffers 也是 0,说明被驱动表根本没被碰。这就是「0 次或 1 次」里 0 次的来源:驱动侧建出的哈希表为空,探测侧就没有必要执行。
2.3 实验二:恒假条件让整个哈希连接不执行
再加一组更极端的,用恒假条件 1=2:
SELECT /*+leading(t1) use_hash(t2)*/ * FROM t1, t2 WHERE t1.id = t2.id AND 1 = 2;执行计划里多了一层 FILTER:
| Id | Operation | Name | Starts | E-Rows | A-Rows |
|---|---|---|---|---|---|
| 1 | FILTER | 1 | 0 | ||
| 2 | HASH JOIN | 0 | 100 | 0 | |
| 3 | TABLE ACCESS FULL | T1 | 0 | 100 | 0 |
| 4 | TABLE ACCESS FULL | T2 | 0 | 118K | 0 |
注意 HASH JOIN 这一行的 Starts 是 0,两张表的 Starts 也都是 0。Predicate Information 里 FILTER 的条件是NULL IS NOT NULL,优化器在运行时直接短路,整个哈希连接一次都没执行。这组数据把「0 次」的含义补全了:不只是被驱动表可能 0 次,整个连接操作本身都可能 0 次。
2.4 把三组数据整理成可检索的笔记结构
三组实验对照下来,结论就清晰了:哈希连接中,驱动表和被驱动表的访问次数只可能是 0 或 1,不存在嵌套循环那种被驱动表反复扫描的情况。我把每组实验整理成固定字段:SQL 文本、提示、Starts、A-Rows、Buffers、结论一句话。这种结构化笔记特别适合后续接进 AI 做检索,因为字段固定,模型容易对齐。
3. TaoToken 前置准备:统一 Key 与通道配置
笔记整理好之后,下一步是把它接进 AI 工具做长期检索和追问。这里用 TaoToken 做统一 Key/API 通道,好处是多个工具共用一套凭证,不用每个客户端单独配。
先到控制台创建 API Key:
https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite创建完 Key 之后,接入文档在这里,不同客户端的配置方式都有说明:
https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewriteAPI 基础地址统一用:
https://taotoken.net/api如果你主要做长期编码和 Agent 类任务,可以看 Coding Plan 的说明:
https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewriteKey 管理页面在这里,方便后续轮换和排查:
https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite注意:Key 只创建一次就够,多个客户端共用。不要把 Key 写进会提交到代码仓库的文件里,用环境变量或本地配置文件承载。
4. 可复制的 settings.json 配置骨架
下面这份配置骨架可以直接改 Key 后使用。以常见的 AI 编码工具配置为例,把模型通道指向 TaoToken 的统一地址:
{ "provider": "taotoken", "apiKey": "sk-替换成你在控制台创建的Key", "baseUrl": "https://taotoken.net/api", "model": "claude-sonnet-4-20250514", "maxTokens": 8192, "temperature": 0.3, "timeout": 60000, "retry": { "enabled": true, "maxAttempts": 3, "backoffMs": 1000 }, "context": { "notesDir": "./oracle-notes", "includePatterns": ["*.md", "*.sql"], "maxContextFiles": 20 } }几个参数说明一下。temperature 设 0.3 是因为技术笔记检索需要稳定输出,不需要太多发散。maxContextFiles 控制在 20 以内,避免一次塞太多文件把上下文撑爆。retry 部分建议保留,网络抖动时自动重试比手动重跑省事。
如果你用的是 Claude Code 这类工具,配置入口和字段名可能略有差异,参考接入文档里的对应章节调整即可:
https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite配置写好后,把 Oracle 笔记目录指向 notesDir,模型就能在追问时引用你整理好的实验数据,而不是凭空编执行计划。
5. 验证请求:确认通道打通并能检索笔记
配置完成后,先做一次最小验证,确认通道可用。用 curl 发一个简单请求:
curl -X POST https://taotoken.net/api/v1/messages \ -H "Content-Type: application/json" \ -H "x-api-key: sk-替换成你的Key" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "claude-sonnet-4-20250514", "max_tokens": 256, "messages": [ {"role": "user", "content": "用一句话说明 Oracle 哈希连接中驱动表的访问次数范围"} ] }'预期返回里应该包含类似「0 次或 1 次」的表述。如果返回正常,说明 Key 和通道都没问题。
接着做笔记检索验证。在 AI 工具里提问:
根据我整理的 oracle-notes 目录,实验一中 T2 表的 Starts 和 Buffers 分别是多少?为什么?如果配置正确,模型应该能引用你笔记里的具体数值(Starts=0,Buffers=0),并解释是因为驱动侧哈希表为空导致探测侧未执行。这一步能验证两件事:通道通了,笔记也确实被检索到了。
想直接在对话里验证模型对哈希连接的理解,可以用模型对话入口:
https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite6. 本篇常见错排查
6.1 请求返回 401 或鉴权失败
最常见的原因是 Key 复制时带了空格,或者请求头字段名写错。Anthropic 风格用x-api-key,OpenAI 风格用Authorization: Bearer,两者不要混用。先到 Key 管理页确认 Key 状态正常:
https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite6.2 模型答非所问,没引用笔记
先检查 notesDir 路径是不是相对路径写错了。相对路径是相对于工具的工作目录,不是配置文件所在目录。建议先用绝对路径验证一次,确认能检索到再改回相对路径。另外 includePatterns 如果只写了*.md,SQL 文件不会被纳入,实验数据就检索不到。
6.3 执行计划数据对不上
如果你复现实验时 Starts 和笔记里不一致,先确认统计信息是否更新过。动态采样(dynamic sampling)在不同数据量下可能给出不同的估算,进而影响连接顺序。可以在 SQL 里加/*+dynamic_sampling(0)*/排除干扰,或者先收集统计信息再跑。
6.4 上下文超限报错
maxContextFiles 设太大,或者笔记文件单个过大,都会触发上下文超限。把大文件拆成按实验分节的小文件,每个文件控制在几百行以内。哈希连接这类实验笔记,按「基线 / 实验一 / 实验二」拆成三个文件就很好检索。
6.5 连接超时
timeout 默认 60000 毫秒,如果笔记量大、检索慢,可以适当调大。但更根本的优化是减少单次检索的文件数,用更精确的提问缩小范围,而不是靠加大超时硬扛。
7. 把笔记接进长期工作流
哈希连接这三组实验的价值,不在于记住「0 次或 1 次」这个结论,而在于你亲手对着 Starts、A-Rows、Buffers 读了一遍数据。笔记整理成结构化字段后,接进 TaoToken 统一通道,后续遇到执行计划相关的疑问,直接追问就能调出当时的实验数据,不用再翻书翻截图。
长期做 Oracle 优化笔记的话,建议把 Coding Plan 也用上,Agent 类任务和编码场景共用一套 Key,省去反复切换配置的麻烦:
https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite配置骨架里的 retry 和 context 两段,是我踩过坑之后加上的——前者应对网络抖动,后者控制检索范围。你可以先按默认值跑通,再根据自己的笔记规模微调。