MYSQL大数据量 join操作的性能调优:欺骗查询计划器以命中HASH JOIN

笔者当前所在mysql版本为8.0.29;mysql在8.0.20,8.0.23逐步演进并完善了部分hash join的功能。

hash join我理解的算法基础即基于hash的桶算法。本质相当于创建一个hashMap,将连接条件作为key直接存入,本质算法复杂度为O(2n)(查询计划会并发扫表实际为n),而循环算法复杂度为O(n²),索引命中的算法下,假设索引分支性能优化优化数量为10倍,也仅仅是O(n²/100),是完全不可同日而语的。但mysql的join语法任然以索引命中优先,并且由于优化器优化顺序的问题,极难命中hash join。本文介绍一种几乎可以稳定命中hash join的方式。

hash join在当前版本中无法命中最基础的原因是链接条件中存在like操作与or链接。在实测过程中部分or操作也会导致hash不命中的问题。尽可能视情况使用union语句处理(主要是union带来的多次扫表的性能取舍)。

hash命中的优化思路主要是使用子sql分别扫表,有了临时表就可以直接用临时表进行hash过程了。

现有如下原始sql:

SELECT
	a.*,
	b.* 
FROM
	test2 a
	LEFT JOIN test1 b 
    ON a.test21 = b.test11 AND b.test12 = '1' AND test13 = '2'
	WHERE a.test22 = '1'
    

先将a表全表与b表ON操作后再where是非常低效的。初步优化为

SELECT
	a.*,
	b.* 
FROM
	( SELECT * FROM test2 WHERE test22 = '1' ) a
	LEFT JOIN ( SELECT * FROM test1 WHERE test12 = '1' AND test13 = '2' ) b 
    ON a.test21 = b.test11

此时获取到了先缩小结果集后join的性能优化。并且语义上已经符合了hash join的触发标准,即没有索引列存在(因为是临时表)。但实际的执行过程中,查询优化器首先忽视你的子sql结构,将它直接展开为普通sql从而获取到正常的查询计划仍然直接对test1,test2物理表进行join操作,从而获取到了物理表字段上的索引,从而使用其他的循环join算法(真的非常蛋疼)。验证这一步可以复杂化表sql后查看执行计划:

SELECT
	a.*,
	b.* 
FROM
	( SELECT * FROM test2 WHERE test22 = '1' ) a
	LEFT JOIN ( 
        SELECT * FROM ( SELECT * FROM test1 WHERE test12 = '1' ) b1 WHERE test13 = '2' 
    ) b ON a.test21 = b.test11

使用EXPLAIN或EXPLAIN FORMAT = tree查看查询计划,会发现第二条和第三条sql查询计划是一样的,从而判断mysql查询计划对子sql产生了破坏性优化。

我用了一种愚蠢又粗暴的做法干预查询器,对每个join的子表做一次全字段无意义的group by操作:

SELECT
	a.*,
	b.* 
FROM
	( SELECT test21, test22, test23 FROM test2 
		WHERE test22 = '1' 
		GROUP BY test21, test22, test23 ) a
	LEFT JOIN ( 
	SELECT 
		test11, test12, test13 
		FROM test1 
		WHERE test13 = '2' AND test12 = '1' 
		GROUP BY test11, test12, test13 
	) b ON a.test21 = b.test11

果然骗过了查询计划器,查询计划器不会对group by操作破坏性优化,EXPLAIN FORMAT = tree查看查询计划发现已经变成hash join。这种方式挺稳定的,没有发现满足hash条件时不可触发的情况。group by不影响扫表次数,浪费一些CPU资源微乎其微。

在我的生产环境测试过程中,三张二十万数据的子sql结果集相互join的过程只花了0.5秒,sql时长几乎等同于最慢的一张表的扫表速度,join过程微乎其微。但桶计数的方式对内存压力较大,默认的16M临时表大小不够祸祸,可以另寻教程调配此参数(我直接设成了4个G,不知道会不会有什么隐患,32G的机器,配置后生产环境机器内存最多能吃到百分之七十,但现在来看稳定性可靠。)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
MySQL中的哈希连接(hash join)是一种连接算法,用于将两个表的数据进行连接操作。它使用哈希表作为中间数据结构,在内存中构建一个哈希表,然后根据哈希值将相同键值的行连接起来。 当执行一个哈希连接时,MySQL会选择一个较小的表作为驱动表(也称为外部表),并将其加载到内存中的哈希表中。然后它会扫描另一个较大的表(也称为内部表),并将每一行的键值与哈希表中的键值进行比较。如果存在匹配的键值,则将两个表中的相应行进行连接。 哈希连接适用于以下场景: 1. 连接的两个表中,其中一个表非常小,可以完全加载到内存中。 2. 连接的两个表没有适合使用索引进行连接的列。 要使用哈希连接,可以在查询中使用JOIN关键字,并指定连接类型为HASH。例如: ``` SELECT * FROM table1 JOIN table2 HASH (join_column) ON table1.join_column = table2.join_column; ``` 在执行哈希连接时,MySQL会自动选择使用哈希连接算法还是其他连接算法,如嵌套循环连接(nested loop join)或排序合并连接(sort merge join),具体取决于查询的复杂性、表大小和可用内存等因素。 需要注意的是,哈希连接可能需要较大的内存空间来构建哈希表,因此在使用哈希连接时要确保系统具有足够的内存资源。此外,哈希连接在某些情况下可能会导致性能下降,因此在实际使用中要根据具体情况进行评估和调优。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值