一次千万量级数据优化之旅

2013年中,毕业第三年,初入上海,入职某大型物流公司,开启了一段成长之旅

入职第三个月,很焦灼,从一个小公司出来,原来天不怕地不怕,技术我就是老大,现在公司大了,感觉人人都是大牛,我只不过是混进来的小老鼠。一段时间下来,感觉总是怂着也不是办法,怎么办?找活干,我们经理倒是爽快,好硬一块骨头甩过来,那是完全超出自己能力范围的一个东西,怎么说呢,就像,拳台上的邹市明突然听到自己的对手是泰森。。完全不是会不会被咬耳朵的问题,,,

怎么办,不能怂啊,只能把千万只羊驼关在心里,让他们自由奔跑,自己呢,默默的去解决问题,不过说实话,碰到这种没有一毛钱头绪的问题,反而让我很兴奋,感觉脑细胞完全不受限制的恣意奔跑。。。哎 哎 哎,你左脑的细胞跑去脑干干嘛,,,,

冷静下来,开始对问题分析。

业务需求:

一线部门,需要根据查询条件,查询出本部门不同级别用户的数据情况,多么简单的一个需求,我看了下,无非是张几5千万级别的数据表查询,哼哼,。。哼哼。。。哼哼。。

1.暴露问题,用最蠢的办法,把多个查询语句一一写出来,最复杂的一个sql500行左右,包含了各种子查询,子查询A统计表A,子查询A嵌套子查询B,子查询B统计查询join表C,子查询D分组嵌套子查询C,DC查询结果关联子查询B,子查询A关联表BED,得出查询列F.....编不下去了,总之各种子查询结果统计,分组,排序,时间的对比,

2.我在考虑要不要辞职,我的职业生涯可能就这么跪了,

3.冷静一想,不行我是要做DNA的男人

4.运行各个查询,保证整个查询结果是和正确的,起码保证了语句是可用的了

5.来来来,我们用线上备份时间跑一下

6.开始!1秒,2秒,,,,125秒,我们去抽根烟,说不定结果就出来了,

7.400秒,,,450秒,,,咱们再去抽一根把。。。

8.2058秒,两盒烟抽完了。。。kill

9.explan 语句,一个索引都不走。。。怎么可能会走索引,里面包含了group by,时间函数,加减运算。。还有不知名的排序。。。而表中的索引寥寥。。

10.

:DBA 这个表 这个表 还有这个表要建索引

:你摇头什么意思?什么线上数据会受影响?不就是建几十个索引么、、、

:.把你领导叫过来

:.jim哥,我错了,我态度不好,恩 下次不敢了,好,每人一个七喜没问题

11.TMD 加了几个索引还是不靠谱啊

12 :DBA姐姐,这是怎么回事?

:.索引不生效?索引覆盖数据太多的话,引擎会认为全表比较靠谱?什么鬼逻辑,hit一下

:.数据库卡死了?我错了,kill掉吧

第一阶段结束:数据量多大,查询逻辑复杂,导致建立的索引很多时候是失效的,而且索引建立过多,线上数据在新增和修改,会受到很大的影响,在数据变更的过程中,不仅是对数据操作,相关的索引文件也有进行变动,导致操作性能低下

也开始意识到,这种数据量下,复杂的数据查询逻辑下,全靠索引解决不了一毛钱问题

不得不召唤我的脑干细胞,来解决问题

不如就从表结构入手吧,既然数据量太大,那就分表怎么样

13 :.DBA姐姐,我想到一个好办法,你觉得分表怎么样

:分表?你咋不上天?

:我刚从天上下来

:分手可以 分表不行

:为什么?

:因为你太帅了,我们不合适

:我问你为啥分表不行?

:分表的话,涉及到分表的规则,路由规则,另外你们之前写的所有数据操作的逻辑都要重新写,你问你们领导见

:不用问我们领导,这个事情我拍板?

:???

:不分就不分 有啥大不了

:分区总行吧

:分区可以

:那就分区吧

:回去把规则定了,看下查询有没有效果

14,:进入到分区探索阶段,问题:这么多张表,按照什么进行分区?那些表要分区?分区后sql语句如何调整

15:解决查询问题:抽象一点来说,每个查询语句无论多复杂,包含三部分内容:

一,结果集的行数(不考虑分页,当未分页的查询速度提升了,分页查询更加快速),

二,结果集的列数(表字段或聚合函数),三,排序分组

行数基本是有where条件决定的,尽量让决定行数的sql效率最优,减少此sql不必要的聚合排序操作,就让他安安静静的做行数查询(A),这部分只查询需要的列,不要贪多哦,以此查询的结果集为基础,再与其他表或子查询进行关联查询(标注1),去达到结果集列数的要求,最后对结果进行统一的分组排序聚合,这是我对于sql优化最主要的几个心得:

其中有几个地方需要注意的,sql索引建立:

最常用的:单列索引,组合索引,函数索引

添加索引依据:where条件中的覆盖度,索引的可选择度

如果在大多数的查询中会使用到某表某列的情况,建议添加索引,但是在执行sql的时候不一定会索引,在oracle中,大部分情况查询结果超过全表的一定比例(10-15%)就不会走索引了,,,尤其是几千万的表数据的情况

再说索引的可选择度,当一个列在标准重复度越低,表示此列作为索引的可选择就越高,此列作为条件做=查询时,效率会更好,为什么?请参考唯一索引。。。。

16:回到正题,分区情况下,选择了主表(基本靠这个表来过滤结果集行数)中覆盖度最高的字段作为分区字段,进行比较常规的range分区,就是按照这个字段的从小到大,分成1000个区间,每个区间为一个分区表,其他几张表中,存在这个字段则进行分区,否则暂时不分区,明确下整个查询中使用率最高的两个字段一个是部门id字段,一个是用户id,部门字段为分区字段,用户id字段作为非分区表的主要关联字段,这样问题就变成了,在一个分区表中的结果集与其他表或者分区表的联合查询,速度果然马上上来,起码一根烟的时间可以看到结果,平均在10秒,

17:已经有很大进展,感觉看到了希望,接下来就是怎么把查询更进一步,再说明下问题,部门字段是有层级关系的,如果单纯查询指定的部门其实问题不大在2,3秒是可以的,但是如果去查询这个部门的下属部门然后进行统计,查询效率直线下降,怎么办,?

18:离线计算,先通过之前准备的各个sql生成的结果存储在各个表中(表分区),在接受查询时,不再通过各个sql实时计算而是去拿这些表中的数据,减少了很多不复杂的计算过程(聚合,排序,函数),然后用结果集去关联一些表拉取需要的列数据,这个离线表会每天进行一次更新,只对有变更的用户数据进行,只扫描前一天更新数据,属于T_1的增量更新办法

怎么更新?存储过程啊,,千把行的存储过程,每天更新一次,不知道现在维护这个代码的同学是否还健在。我也来解释下为什么要少用存储过程,其实存储过程对于数据操作性能很高,毕竟是本地操作,但是plsql本是也是种语言,要定义产量,要定义游标,要关闭游标,要做异常处理,要记录错误日志,对于开发人员整体的代码编写和维护成本还是很高的,毕竟不是随便拉一个人过来就可以噼里啪啦写存储过程的,这也导致了储过程对程序员而言,可读性很差导致做完这个需求后,我有段时间在回忆怎么写java,,

完成这一步以后,基本已经完成了一半,还要再找更忧的方式比如说部分离线计算的表(运算量比较大),换成物化视图(区别于视图),因为物化视图支持自动更新(采用定时刷新),减少了存储过程的维护量,而且物化视图也支持索引,分区

1:8:还有什么问题?那就是线上数据的重定义

新需求上线时要保证之前需要分区的表把分区做好,但是又不能影响线上的环境,这个时候Oracle强大的地方就要体现了,在线重定义,mysql可没有,

此需求在线重定义的过程,其实就是将需要重定义的表进行备份,然后将备份表进行分区,分区完成后,重建索引(原有全局索引),将备份表重命名为线上表名,然后就没有然后了,

细心的同学肯定会跳出来,有bug,在重定位期间的,线上是有数据更新的,但是备份表拿不到这些数据,这就是Oracle在线重定义的奥妙,数据的同步,

如果换成mysql应该怎么玩?备份表,查询当前最大manxid,对备份表进行分区操作,将原表中 id>maxId 的行复制到备份表,将原表重命名,然后备份表重命名,重命名阶段会有短时间的间隔,会有部分业务失败,如果mysql现在又更高级的功能支持这个操作的,请指正

基本上这整个优化以及完成,在回头看下结果,

首先对于部分表进行分区,选择那些表,当时使用的原则是影响行数结果的表,查询条件中使用最频繁列,那些表分区?包含这个列的都可以进行分区,具体看业务,建立合适的分区索引

2.对于未分区的表,建立合适的索引:组合索引  函数索引

3.对于计算复杂部分进行离线运算,提前将查询结果集中存放在表中或物化视图,建立分区,分区索引

4.维护工作,存储过程,在线重定义,

5.各个分区表的后续维护,因为数据时不断增长的,分区列的数据也在增长,定时的进行分区表的重新分区(或在建立分区时,将分区范围放大,比如目前分区列数据只有1-1000,分十个区,提取假想目前分区列数据时1-10000,分一百个区,保证未来一段时间,不需要重新分区操作)

根据这个结果,在未分页情况下,查询性能基本在1秒以内,经过压测,各个场景在200次请求/S的并发下,也是在1.1秒内,也是未分区情况,功能上线后,分页情况下基本都是0.5左右,算是一个合格的交付

疑问解答,群里很多伙伴,提到缓存的方式,将查询结果缓存起来,这个方案基本上不可行的,查询条件是多变的,结果是随时变的,不太适合这个场景

复盘:

· 这是四年前 能拿出来最好的方案,现在看来总体思路是可以的,但是不够fashion

如果是我现在要怎么做?

大体思路,依然对需要的表进行分区,统计结果集放在不同表中,直接供查询使用,但是会放弃物化试图和存储过程,物化试图没有实质性的提升,存储过程成本太高不可控,总不能线上debug存储过程,虽然oracle支持类似操作。离线更新方式用实时计算方式代替,无论是采用流时计算还是看上去像流式计算,总有办法达到目的,对统计的结果集表采用分表的方式,这些完成后,此类查询可以独立为一个服务,完全脱离了原有系统,无论从数据库层还是代码层面,可以独立部署发布,不会有后遗症,

缺点就是,相比之前的方式开发量更多,测试成本高,引入了诸多新功能风险也增加了,实时计算的幂等处理,数据丢失补偿等,不如存储过程高效直接,增加分表,表路由等问题

优势,摒弃存储过程将数据计算放入代码中,可读性高,采用分表的方式在实时计算方面,吞吐量也会上升,统计过程更加平稳,完全脱离原系统,不存在耦合问题

具体可以参考下图

欢迎大家加微信骚扰:treenpool

请持续关注,http://treenpool.com国内第一款专注于知识体系搭建工具

你可能感兴趣的:(一次千万量级数据优化之旅)