从 BNL 到 hash join:一次 join 提速 20 倍的升级

举报
yd_275379215 发表于 2026/10/25 08:25:06 2026/10/25
【摘要】 一个核心报表查询join两张大表,5.7时代跑了42秒,迁到8.0之后同一个SQL只跑2.1秒,SQL和索引都没动。团队一开始以为是新机器的功劳,EXPLAINFORMAT=TREE一看,执行计划里赫然写着Innerhashjoin——不是硬件,是优化器换代了。5.7处理无索引可用的join靠BlockNestedLoop(BNL):把驱动表按join_buffer_size分块装进内存,被...

一个核心报表查询 join 两张大表,5.7 时代跑了 42 秒,迁到 8.0 之后同一个 SQL 只跑 2.1 秒,SQL 和索引都没动。团队一开始以为是新机器的功劳,EXPLAIN FORMAT=TREE 一看,执行计划里赫然写着 Inner hash join——不是硬件,是优化器换代了。

5.7 处理无索引可用的 join 靠 Block Nested Loop(BNL):把驱动表按 join_buffer_size 分块装进内存,被驱动表全表扫,逐块比对。join_buffer_size 默认 256KB,大表场景下被驱动表要被反复扫几十上百遍,代价是乘法级的。8.0.18 引入 hash join:用驱动表在内存里建一张哈希表,被驱动表只扫一遍,逐行探测哈希表。复杂度从"多遍扫描"降为"一遍扫描加哈希探测",这就是那 20 倍的来源。

复现一下计划的差异:

-- 两表 join,关联列上都没有索引
EXPLAIN FORMAT=TREE
SELECT COUNT(*)
FROM t_order o
JOIN t_user_ext e ON o.uid = e.uid
WHERE o.create_time >= '2026-10-01';

-- 5.7:Block nested loop,被驱动表反复扫描
-- 8.0:Inner hash join (cost=...)
--     -> Table scan on o
--     -> Hash (rows=8000000)
--         -> Table scan on e

但 hash join 不是万能护身符。建哈希表的一侧要能装进 join_buffer_size,装不下就分批做,增加开销;等值条件才支持,非等值 join 还是老路子。我们在 8.0 上实测调整 join_buffer_size 从默认 256KB 到 16MB,那张报表又快了 30%,但再往上收益递减,内存占用却线性涨,最终定在 8MB。

升级后的回归测试也值得展开:把历史 SQL 全量抓下来(pt-query-digest 的 top 1000)在 8.0 环境回放,比对执行计划变化。我们发现 97% 的语句持平或变好,3 条劣化的都和子查询改写策略有关,用 optimizer_switch 关掉对应特性解决。版本升级的工程量一大半在这些边角。

这个案例还引出一个工程判断:hash join 是"没有索引时的兜底",有索引的等值 join 依然首选 ref/eq_ref 计划。升级 8.0 后更重要的动作是重新审视那些"因为慢而绕开 join 拆成多次查询"的老代码——优化器进步之后,有些拆分反而不如合回去。当年为绕 BNL 而设计的应用层 join,现在成了新的性能瓶颈。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。