谷歌mysql in如何优化?有哪些方法和注意事项

文章摘要

谷歌mysql in的性能瓶颈分析谷歌mysql in查询是我们写SQL时经常用到的语法,select * from user where id in (1,2,3)”,看起来简单,但数据量大了就容易掉链子,我之前维护一个用户行为日志表,表数据有2000万条,有次用“select count(*) from l……

谷歌mysql in的性能瓶颈分析

谷歌mysql in查询是我们写SQL时经常用到的语法,select * from user where id in (1,2,3)”,看起来简单,但数据量大了就容易掉链子,我之前维护一个用户行为日志表,表数据有2000万条,有次用“select count(*) from log where user_id in (1001,1002,...,5000)”查活跃用户数,结果等了快半分钟才出结果,后台监控还告警了慢查询,后来分析执行计划发现,当in子句中的值数量超过200个时,MySQL优化器就“摆烂”了,原本该走user_id索引的查询,直接变成了全表扫描,就像本来能坐电梯,结果硬生生爬了20层楼。

除了值太多,索引失效也是常见问题,比如in子句里的值类型和字段类型不匹配,像字段是int型,in里却传了字符串“1,2,3”,MySQL会偷偷做类型转换,索引自然就用不上了,还有一种情况,表分区表用in查询时,如果分区键没在in条件里,数据库得扫描所有分区,性能直接打五折。

谷歌mysql in优化的常用方法总结

优化谷歌mysql in查询,第一个要学的就是“给in子句瘦身”,我试过把in后面的500个值拆成10组,每组50个,用“in (1-50) or in (51-100)...”的方式查询,再把结果合并,没想到效果立竿见影,之前30秒的查询缩短到5秒内,因为每组值数量少了,优化器愿意走索引了,不过拆的时候要注意,别拆太多组,不然SQL太长也会影响解析效率。

换成join查询也是个好办法,select * from order where user_id in (select id from user where status=1)”,这种子查询in可以改成“select o.* from order o join user u on o.user_id=u.id where u.status=1”,我之前帮朋友优化电商订单查询时这么改,原本15秒的查询直接跑到2秒,因为join能更好地利用表之间的索引关系,比in子句“单打独斗”效率高多了。

给in条件字段建合适的索引更是基础操作,比如用户表按“status,id”建联合索引,查询“where status=1 and id in (1,2,3)”时,索引就能从status开始匹配,快速定位到符合条件的id,不过要注意,索引不是越多越好,字段更新频繁的话,多索引反而会拖慢写入速度,就像衣柜里衣服太多,找起来反而费劲。

谷歌mysql in优化的具体操作步骤

优化谷歌mysql in查询得一步一步来,不能瞎改,第一步肯定是看执行计划,在SQL前面加“explain”,看看type列是不是“range”或“ref”,key列有没有用到预期的索引,要是type是“ALL”,key是“NULL”,说明全表扫描了,得赶紧想办法。

谷歌mysql in如何优化?有哪些方法和注意事项

第二步分析in子句的值,如果值是固定的,比如系统配置的白名单ID,直接写成常量in查询没问题;要是值是动态从别的表查出来的,优先考虑用join代替,我之前处理过一个报表查询,in子句里的值是从另一个表查的,大概有300个,改成join后,执行时间从8秒降到1秒,因为MySQL对join的优化比子查询in更成熟。

第三步测试不同优化方案,同样的查询,拆分成多个小in、用join、建临时表,都跑一遍,看哪个性能最好,记得测试时用生产环境的真实数据量,小数据量下看不出差异,就像用玩具车测试不出越野车的性能。

谷歌mysql in与exists查询的效率对比

很多人纠结谷歌mysql in和exists哪个效率高,其实得看场景,我做过一个实验,两个表:user表(100万数据)和order表(500万数据),查“有订单的用户”,用in是“select * from user where id in (select user_id from order)”,用exists是“select * from user u where exists (select 1 from order o where o.user_id=u.id)”,当order表的user_id有索引时,exists跑了0.5秒,in跑了1.2秒;但如果order表没索引,in反而比exists快0.3秒。

秘密在于,exists是“外表驱动内表”,只要找到一条匹配就停止,适合内表数据量大的情况;in是“内表驱动外表”,先把内表的结果查出来,再和外表匹配,适合内表数据量小的情况,就像找东西,exists是逐个检查每个抽屉,找到就收手;in是先把所有可能的地方列出来,再挨个核对。

谷歌mysql in优化的常见误区解读

优化谷歌mysql in时,很多人踩过坑,最常见的就是“盲目加索引”,觉得只要字段在in条件里,建个索引就万事大吉,我见过有人给一个只有100条数据的配置表,在in条件字段上建了3个索引,结果查询快了0.01秒,写入却慢了2倍,纯属得不偿失,小表根本不需要索引,全表扫描比走索引还快,就像去便利店买瓶水,没必要开车去。

还有人觉得“in的值越少越好”,其实不是绝对的,如果in后面只有10个值,但表数据量特别大,比如1亿条,这时候就算值少,没索引一样慢;反过来,值多但有合适的索引,也能跑得很快,关键不是值的数量,而是优化器能不能用上索引,就像考试能不能考好,不取决于题目多少,而取决于会不会做。

谷歌mysql in优化的实际案例分享

去年帮一个教育机构优化数据库,他们有个“学生成绩查询”功能,老师选多个班级,查这些班级的学生成绩,SQL是“select * from score where class_id in (班级ID列表)”,班级ID列表有时多达200个,查询经常超时,我先看执行计划,发现class_id有索引,但type是“ALL”,果然全表扫描了。

第一步,我把in后面的200个班级ID拆成5组,每组40个,写成“class_id in (1-40) or class_id in (41-80)...”,跑了一下,执行时间从25秒降到10秒,有进步但还不够,第二步,发现他们的score表按“exam_id”分区,而查询里没带exam_id条件,导致扫描了所有分区,我让他们在查询里加上“and exam_id=当前考试ID”,这样只扫描当前考试的分区,执行时间又降到3秒,最后一步,把拆分成的多个in子查询用union all合并,避免“or”带来的优化器误判,最终稳定在1.5秒左右,老师再也没抱怨过查询慢了。

谷歌mysql in优化的工具推荐及使用

优化谷歌mysql in查询,光靠手改SQL不够,还得有工具帮忙,MySQL自带的explain工具是基础,能看执行计划,但不够直观,我常用的是“Percona Toolkit”里的pt-query-digest,能把慢查询日志里的谷歌mysql in查询统计出来,告诉我们哪些in查询最耗时,值数量多少,有没有用索引,就像给慢查询做了个体检报告。

还有“Navicat”的查询分析器,可视化展示执行计划,索引使用情况一目了然,适合新手,不过这些工具都得结合实际数据量测试,不能只看工具给的建议就改SQL,就像医生开药还得看病人具体情况呢,目前这些工具的官方暂无明确的定价,开源工具可以免费使用,商业工具需要联系厂商咨询。

常见问题解答

谷歌mysql in查询最多支持多少个值?

谷歌mysql in查询对值的数量没有官方上限,但实际使用中超过200个就可能出问题,我试过一次传1000个值,SQL直接报错“packet too large”,后来查资料才知道,MySQL的max_allowed_packet参数限制了数据包大小,值太多会超过这个限制,而且值越多,优化器越难走索引,一般建议控制在200个以内,超过的话就拆分成多个小in查询或者用join代替,这样更稳妥。

谷歌mysql in和not in哪个效率更低?

谷歌mysql not in效率通常比in低很多,尤其是子查询的时候,select * from user where id not in (select user_id from order)”,如果order表的user_id有null值,not in会直接返回空结果,因为null和任何值比较都是unknown,而且not in很难用上索引,基本都是全表扫描,而in至少在值少的时候能走索引,所以尽量别用not in,改用left join ... on ... where ... is null,效率会高不少,亲测有效。

谷歌mysql in里的值有重复会影响性能吗?

谷歌mysql in里的值重复会影响性能,in (1,1,2,2,3,3)”,MySQL会先去重再查询,但去重过程需要额外的计算时间,尤其是值很多的时候,我之前测试过,1000个值里有500个重复,查询时间比去重后的500个值慢了30%,所以传值给in子句前,最好在代码里先去重,减少数据库的负担,就像出门前先整理好背包,别带重复的东西。

谷歌mysql in查询可以用索引吗?

谷歌mysql in查询可以用索引,但得满足条件,首先in子句里的字段得有索引,其次值的数量不能太多(一般建议200个以内),值的类型要和字段类型一致,不能有隐式转换,比如字段是int型,in里传字符串“1,2,3”,MySQL会转成int,但索引就用不上了,我之前有个表,id是int型,in里传了字符串id列表,结果执行计划显示全表扫描,改成int型后马上用上了索引,查询时间从10秒降到1秒。

谷歌mysql in和临时表哪个更适合大量值查询?

如果谷歌mysql in里的值超过500个,用临时表比直接in查询更好,我之前处理过一个需求,需要查1000个用户的订单,直接用in查询跑了20秒,后来改成把用户ID插入临时表,再用join查询,执行时间降到5秒,因为临时表可以建索引,join时能高效匹配,而大量值的in查询优化器很难优化,不过临时表也有缺点,需要额外的IO操作,小量值的话没必要,就像买东西,少量东西快递就行,大量东西才需要找搬家公司。