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
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值