优化千万级mysql 震惊 原来5.5k月薪的你也能

震惊 原来5.5k月薪的你也能 优化mysql 千万级

    • 前言
      • ■ 任务
      • ■ 第一波操作
      • ■ 第二波操作
      • ■ 沉淀
      • ■ 第三波操作
    • 优化招数
      • ■ count查询和数据查询 分离
      • ■ 延迟关联
      • ■ 妙用索引(*)
        • 1.本人对索引的理解
        • 2.解析索引生效情况
        • 3.建立高性能、可复用的索引
    • 改变分页机制
    • 最后

前言

■ 任务

慢sql优化改造,大表数据分页机制性能改造 ,提升大数据查询速度

■ 第一波操作

接到这个任务的第一想法是:有点意思,终于能有个mysql调优方面的机会。然后就是提取sql,尽情的摆弄它,接着就是发现了调整left join的顺序,速度快了3分之1,再慢慢演变成面对搜索引擎优化,结果显然是失望的。搜索后你会发现关于大数据mysql优化方面的资料、帖子如出一撇 ——加硬件、改配置、分区、分库、分表、修改字段类型、用缓存分担数据库压力、读写分离、冷热数据分离、历史数据清理。越搜越气,越气越select,越select越慢。。。第一次优化宣告失败

■ 第二波操作

大佬们的帖子分区分库分表其实也没有毛病,但显然这不是我想要的,一时之间我竟然也默认了mysql数据只能支持起几百万的数据,赶紧停住这个雪球的滚动吧。然后就是
报告经理:“mysql不行,数据太大了,优化不了,得清理历史数据。”
报告经理:“分页优化不了,limit越后面越慢我也没办法。”
报告经理:“其实limit第一页也还可以,就是查询count耗时太长。”
无奈,经理回复:“肯定有办法的,不然那些大公司怎么处理。”
经理提出了一个方案:新增字段自增主键id,每次查询都传入上次查询最后一个id,以缩小查询范围。 以我敏锐的程序嗅觉,这会给自己挖大坑,不假思索——
报告经理:“这个有好多where条件,那样limit就不准了。”
其实就是乱七八糟胡塞了一个借口,但是一时也唬住了经理。O(∩_∩)O哈哈~ 这个方案的实现就流落到另一个同事的手上。他给一个近千万级的表加自增字段,果不其然数据库崩。。。一下午的紧急处理。我到目前都没去想这种方案可不可行,1、给大表新增字段造成的卡顿、2、传参数需前端安卓协助。单单这两点就让我放弃这种思路。。。。第二次优化宣告失败

■ 沉淀

我有一个习惯,就是有需求、问题一定要按已有的知识体系去解决,凭借着不错的逻辑思维,通常能凭借着‘小刀’闯天下,‘切菜’也罢、‘宰牛’也罢。这也造成了不会及时的去更新自己的知识储量。当然,遇到了棘手的问题后,我便会选择沉淀一段,而后再尝试能否解决。在b乎前辈的推荐下,我选择了《高性能mysql第三版》。看着是有点头疼,要在短短时间内消化完这么一本经典的奇书压根不现实,所以我选择性的翻阅了优化部分,区区几页,如获一师。

■ 第三波操作

以下为一个5.5k的码畜 对mysql近千万级大表的优化操作 下文皆为innodb引擎

优化招数

■ count查询和数据查询 分离

用分页插件的话,查询count是select count(1) from (原sql),分页插件仅适用于一些不耗时的sql查询既不耗时又节省开发时间。后端第一次查询count,count()为0直接返回空分页,不执行具体查询,让无数据返回达到最快。

■ 延迟关联

一般sql查询有大量的left join,笛卡尔积,非常损耗时间。例如1000000* 100* 100,就非常恐怖,那为什么我们不能先查出所需要的10,再去* 100* 100呢?也就是10* 100* 100。哇!爽爆了~ 这个就是延迟关联。例如

SELECT
	a.*, b.w,
	c.o,
	d.r,
	e.i
FROM
	a
LEFT JOIN b ON a.q = b.q
LEFT JOIN c ON a.q = c.q
LEFT JOIN d ON a.q = d.q
LEFT JOIN e ON a.q = e.q
WHERE
	e.kk = 1
LIMIT 1,10

延迟关联后

SELECT
	a.*, b.w,
	c.o,
	d.r,
	e.i
FROM
	a
INNER JOIN (
	SELECT
		id
	FROM
		a
	LEFT JOIN e ON a.q = e.q
	WHERE
		e.kk = 1
	LIMIT 1,
	10
) pp USING (id)
LEFT JOIN b ON a.q = b.q
LEFT JOIN c ON a.q = c.q
LEFT JOIN d ON a.q = d.q

此处id作为主键,有覆盖索引不用回表查询,先筛选所需要数据的id,再去关联其它表显然 比 先关联再筛选妙出一个维度,优化效果也是肉眼可见。

■ 妙用索引(*)

一提到大表,第一想法就是建立索引,但是很多人建了索引后,索引生没生效都不知道。索引才是优化的重中之重,没有索引为基础,什么神操作都是白搭,当索引难以再调整后,才会另寻他路。索引是啥?索引生效没?索引怎么建?

1.本人对索引的理解

《高性能mysql》图5-2
优化千万级mysql 震惊 原来5.5k月薪的你也能_第1张图片
看了上面这张图,大致就对B-Tree索引有了一个了解。水平有限,怕误人子弟,就不讲这个概念了,讲讲我对B-Tree索引的感受。
例如:
组合索引(sex,age,time)

组合索引(sex,age,time)
sex age time name ...
1	20	2019 ...  ...
1	20	2020 ...  ...
1	20	2020 ...  ...
1	21	2018 ...  ...
2	20	2019 ...  ...
2	21  2017 ...  ...

第一层 1  										2
第二层 1(20) 1(21)								2(20) 	  2(21)
第三层 1(20)2019 1(20)2020 1(20)2020 1(21)2018	2(20)2019 2(21)2017

当我们
1、where sex=1的时候 (sexagetime
注:索引会定位到1的那一大堆
2、where sex=1 and age >20 and time=2019的时候 (sexagetime
3、where sex=1 and age in(21) and time=2019的时候 (sexagetime
4、where sex=1 and time=2019的时候 (sexagetime
5、where time=2019的时候 (sexagetime
注:索引最左原则指的是组合索引定义(sexagetime)按从左到右顺序走,而不是sql语句where条件的先后顺序。网上经常说到怎么怎么样,索引不生效,走全表扫描,例如上例:并不是说time不生效就完全不走索引了,它还是走(sexage)的,这是一句很有歧义的表达,应该表达为走全表扫描或全索引扫描。

说白了索引就是个按照各种字段有序化,起到一个加速缩小数据范围的过程,组合索引通过一个又一个索引字段不断地缩小数据范围直到不能再缩小为止,所以一般情况下越前列的索引区分度尽可能的高,索引才会越漂亮。

COUNT(DISTINCT column_name)/COUNT(*)
这是一个计算索引区分度的公式,

区分度低的字段不该给索引?不,凡事皆有例外,在这也仍然适用,如果说上例sex,男女各100w,sex索引生效后,理想情况下查询男或者女速度可以翻个倍,效果并不显著,如果男有199w女1w,我们查询女,这
优化千万级mysql 震惊 原来5.5k月薪的你也能_第2张图片
这就是一个非常成功的索引,它将200w的数据范围缩小到了1w。

2.解析索引生效情况

分析sql语句执行情况,大家应该都知道就是EXPLAIN SQL,解读又是一门学问,我的学习方法就是不停的理解网上的explain例子,看多了也就看懂了。
我给个例子如下:
在这里插入图片描述
挑重点讲,
id:
执行顺序:从大到小,id相同从上到下。

possible_keys:
可能会使用的索引,索引有字段能生效,将被列出,但不一定被使用。

rows:
网上一般认为这个是预计扫描行,而我则认为这是经过索引筛选后大致还剩下的数量,这个值可以参考,不可尽信。(我的理解是:100w数据索引过滤后还剩1w,相当于浓缩了一下,同理浓缩后limit 10很快,limit 9990,10很慢,这是一个要点,后面要用到)

key(*):
被使用的索引,不一定在possible_keys中有。
注意:此例使用了组合索引fasts(tenant_id, del_flag, customer_type_id, create_time),即便索引只有tenant_id生效,key列显示的也是fasts。所以看到key用到的是自己想要使用的索引名不要太高兴,它有可能并没有让所有字段生效。
如果你对组合索引是否使用了所有列有所怀疑的话,可以尝试建立例如——fasts1(tenant_id, del_flag, customer_type_id)fasts2(tenant_id, del_flag,)fasts3(tenant_id) 并使用 force index(fasts1) 。。。等等一个一个对比看查询速度是否有所差距,就知道你建的索引是否全部字段都生效。

小结:
我个人认为explain非常好用也是必须要用的,但是也不能盲目的相信它。几乎没有人提到过explain测出来的key有可能不是真实走的索引,我的线上优化过程中就遇到了这种情况,一旦误导了就会对优化造成一定的麻烦,具体的情况已经忘了,现在已经不可复现,所以我在本地模拟了这种情况。
特殊情况:
来看下这条sql,没有强制走哪个索引,explain的结果是走的索引fasts
优化千万级mysql 震惊 原来5.5k月薪的你也能_第3张图片
那么执行一下,可以看到耗时5秒多。
优化千万级mysql 震惊 原来5.5k月薪的你也能_第4张图片
那么调整一下sql,让它强制走fasts索引,结果发现不到1秒
优化千万级mysql 震惊 原来5.5k月薪的你也能_第5张图片
这说明了explain的分析不一定准确,它并没有使用fasts索引,而却列了出来。
然后,我发现我还有一个单列索引create_time。显然这条sql语句存在问题,对create_time字段做了格式化,create_time索引不会生效,并且possible_keys里也不存在这个索引,但是当我把create_time索引从数据库中删除后发现速度降回了1秒内,也就是说删除后才真正的走了fasts索引。而没删除之前和强制走create_time索引效果一样,走了索引,却又没有索引字段生效。
explain下强制走create_time
优化千万级mysql 震惊 原来5.5k月薪的你也能_第6张图片
当然,我在线上所遇到的情况就没这个清晰明了,而是更加复杂,当时好像是多个索引互相串味了,这个涉及到mysql查询优化器的选择,我。。。。。。也不太了解。

3.建立高性能、可复用的索引

上面所说的索引串用,而执行计划又没有找出来,就是由于线上遗留了前辈们建的很多无效的、重复的、不合理的复合索引,没有使用,却还保留在上面,索引太多了让mysql的查询优化器的选择不再那么靠谱。
经常有人说建尽可能少的索引,是因为太多的索引影响了表的插入、删除与更新的速度,这一点不可否认,不过还要加上非常重要的一点——避免对执行计划也就是索引的选择造成负面影响。
① 范围索引必须留在最后一个,因为范围索引的后面索引字段不会生效。

② 尽可能的复用索引,我在数据库经常看到有(a,b,c)索引,竟然还存在(a)(a,b)之类的索引,要知道(a,b,c)是包含这两个的。

③ 用in可以巧妙的少建立索引。例如你有一个搜索条件性别 ,男为1,女为2,全部为不查,有的人可能会建立这么两个索引(…,sex,…)和(…,…)来应对有性别搜索条件,和无性别搜索条件。然后你非要给性别设索引的话,可以只建(…,sex,…)索引,然后修改sql语句,当不过滤男女的时候把男女全列出来也就是

 select ... from a where ... and sex in(1,2)and...

这样就可以巧妙的少建立索引。
④ 我列一些索引不生效的情况。
1、查询(is null)(like ‘%…’)
2、字符串型字段例如:sex用字符串型,sex=1[索引不生效] sex=‘1’[生效]
3、对字段做了函数处理例如上例:date_format(a.create_time, ‘%Y-%m-%d’)=
4、不走索引更快时,查询优化器选择不走索引。时间范围就是一个例子,有说选择性小于17%时就不走了,具体未验证,我是有试过时间范围大到一定时不走索引,但我强制让它走索引时,速度还会快挺多,所以我这个表述也不是很准确。
索引不生效的情况,这些可以网上查,非常多

改变分页机制

索引很难调整了,还是不行就看看其它方法了。传统的分页就是需要查询count,然后再查具体一页的数据。我们公司的项目pc移动都是用同一套分页,而pc那边的查询都有一个日期范围筛选,只要日期范围索引生效,查询速度是非常快的,移动端就不一样了,没有日期筛选,速度非常的慢,而且移动端查询count也是没有必要的,省略掉查询count,对性能提升效果最为显著。
保持一个优良的优化原则:后端改动,前端不动。
例子:移动端需要第3页,5条数据。传统我们是去除limit先查一遍count,有可能是100w,然后查询具体数据limit 10,5,查询count耗时好几秒,limit具体数据是秒查,这样如何优化呢?
我称之为——动态追加下一页法
limit 10,6 为 resultList ;返回count=(3-1)*5+resultList.size ;如果siz为6返回的resultList romove掉最后一条数据
如果resultList.size返回1~5,说明这个查询就是最后一页了,这么计算的count和传统count一致,如果resultList.size返回6,说明查询还可以有下一页,移动端也能判断是否有下一页
,就这么简单的一个小改动,移动端本来查询是卡顿住的变成了只剩下握手放手所损耗的时间。
当时间范围索引没生效的情况下,limit前面一些数据非常快,到后面就不太行了。可是在真实使用场景中,使用者只需要查看limit前面的那些数据,真要看历史数据是可以通过时间范围来定位的。PC端的Web其实也可以采用类似这种的动态追加的分页查询法,只提供用户查询当前页附近的几页,这样无论你有多大的数据,我都不怕了。

最后

路漫漫其修远兮,mysql优化和分页的优化还有很长的路要走,优化是要针对具体的业务而定的,所谓的优化招数都不会是万金油,要想成为强大的优化者,还是要继续不断的学习。过早的优化是万恶之源,尽量地合理建立数据库,减少被几年后接手你的项目的人骂声才是硬道理。

你可能感兴趣的:(mysql)