从一次火烧眉毛的SQL优化,看应用架构规范与敏捷之殇

黄浩 2017-09-18 11:05:00

作者介绍

黄浩:从业十年,始终专注于SQL。十年一剑,十年磨砺。3年通信行业,写就近3万条SQL;5年制造行业,遨游在ETL的浪潮;2年性能优化,厚积薄发自成一家。

 

一、案例

 

公元2015年8月7日,星期五,立秋的前一天;在内陆,在北方,有秋老虎、秋扒皮一说,用于形容立秋那段时间的炙烤程度。但是在美丽的滨海之城深圳,并没有秋老虎、秋扒皮这一说法,因为在深圳,根本就没有严格意义上的秋天。

 

我的美味早餐被“打翻”了

 

从班车下来,走到食堂,短短10分钟路程,已经让我这个体型偏瘦的人汗流浃背,几近湿身。好在食堂的空调可以缓解下暑气。当我还在品尝着煎鸡蛋的可口香味时,一个电话让我连打包带走的时间都没有,直奔项目现场,也全然不顾烈日当空酷暑灼热。

 

生产环境出现了性能问题,由于下午14点给上级领导演示的功能清单包含该功能,所以必须要在上午12点前修复。

 

当我赶到现场时,已经围上了好几个人,凑上去,一股汗渍味直扑而来。这其中我只认识一个人,就是给我电话的SE,其余的都不认识。大家都神情严肃,全然不顾弥漫在四周的汗渍味。

 

扑朔迷离的现象,跌宕起伏的剧情

 

“这个逻辑很简单,就是将Excel的数据合并到数据表中,没有业务逻辑处理,绝对是SQL的问题。”

 

看来他们也刚来不久,还在讨论问题的原因。

 

“那就看下日志,看是哪条SQL处了性能问题。

 

坐在电脑前的应该是这个功能的开发者或维护者,能明显看到他的手在颤抖,以至于鼠标找了很久才点中“服务器日志下载”所在的菜单项。这是必然的,自己做的功能出了问题,尤其是在给上级领导演示的节骨眼上,一种无形的压力自然会干扰到人的正常行为。

 

“你起来,我来操作。”

 

站在一旁的一位高个子看不下去了,几乎是把他从座位上提着衣服拽起来了。

很快,在日志中找到了导致超时的SQL。

 

“看看,我就说过是SQL导致的了,快,黄工,你赶快优化优化这个SQL”

SE显得很兴奋,大有为自己的未卜先知而拍手称快之意。

 

“不对呀,这是一条很简单的insert语句,不会有性能问题吧。”

 

那位高个子小声的说:“insert语句?怎么可能,肯定是找错了,再找找看。”

 

的确,从日志上看,确实是由于一条insert values语句引发的超时。但是一条简单的insert values语句会如何引发超时异常呢?说update或者delete或者还有可能。

 

“要不我们看下应用程序代码吧。”

 

因为在最近的几起优化案例中,有一半是与Java程序代码有关,既然日志的记录存在问题,我就建议回归本原。

 

在继续看代码之前,我在这里先卖个关子,先把该功能的逻辑示意图展现出来,如下图:

 

 

一个蹩脚的设计方案

 

现在,如果你作为该功能的设计者,你该如何设计呢?可以停顿下来,完成设计后再往下看,看下开发人员是如何设计的?又存在哪些更优的设计方案?对比自己的设计,是否是最优的?

 

“这个方案是谁设计的?”

 

还没看到一半,SE按耐不住发飙了。当我看完真个代码后,也为设计人员捏了把汗,确实是一个非常蹩脚的设计。我们来看看设计人员的方案示意图:

 

 

对于DB来说,一个非常简单的数据同步,被设计得如此复杂,用当时的话说“也是一个人才”。

 

这个设计最致命的地方就是频繁的访问DB。

 

整个同步操作被封装在一个事务中,事务的超时被设置为120秒,即2分钟如果这个事务还不能完成提交,就会抛出超时异常。这次的演练测试案例,按照开发人员的设计方案,会产生超达120万次的BD访问,每次访问加处理,按1毫秒计算,120万次的访问也需要120万毫秒,即1200秒,20分钟,不超时才怪。

 

据理力争

 

近一小时过去了,稀拉拉空荡荡的办公区已经人声鼎沸,每个人都在用不同的方式驱赶身上的暑期汗渍,有抬头望空调的,有拿着折扇摇动的,有拽着书本摇晃的,有光手在额头上抹汉的。都在谈论这炙烤的天气,而埋怨空调的“虚情假意”。而在靠东南的一个角落,三五个人已经忘却了周围的一切,正在商讨着解决方案。

 

没得说,这不是单条SQL的性能问题,方案必须得整改。

 

SE当即要求在方案上做出整改:将临时表与正是表对比的步骤放在DB中,其它保持不变。他这样做的理由是:一方面代码改动量小,风险少;另一方面也符合他的设计理念:尽量在应用程序中处理数据。

 

我的方案是:像这种批量数据同步,应该全部在DB中完成。方案如下:

 

 

SE并没有认可我的方案,他给的理由很充分:这样改动太大,目前时间紧迫,根本无法消化。万一弄出点功能上的问题,谁也担当不起。

 

要知道,如果一旦将问题上升到功能的稳定性,基本上都需要让步。而此刻的我也顾不了太多,据理力争:

 

  • 首先Oracle是关系型数据的典范,批量处理这种数据具有先天优势;

  • 其次,批量数据如果放在java端,势必会带来数据的批量迁移,两台服务器间数据传输也是需要消耗诸如IO、CPU、网络带宽等系统资源;

  • 最后,将所有处理封装在SQL中也便于日后维护及扩展,如果有改动,只需要维护SQL语句,而无需改动java代码,而且SQL写起来简洁易读。

 

最后,从我近十年的ETL经验,逻辑处理放在DB中,尤其是批量处理,是最佳实践。

 

经过我的一番据理力争,SE也放弃了他的坚持,决定选用我的方案。

 

方案出来了,谁来实施呢?开发人员怯生生的,惊魂未定的表示自己不会写这么复杂的SQL。

 

“你自己挖的坑,就由你自己来填吧。”SE对着我笑了笑。

 

二、批判

 

两相对比,何其相似

 

该案例与第一个案例《WM_CONCAT优化》既有相似之处,也存在很大的差别,先说相似之处。

 

两个案例最终的方案都是一样的:在应用层面改架构,在DB层面改SQL。也就是说都是因为应用架构引发的性能问题。现在我们就来说说这个架构规范。该项目的总架构师给该项目定了两条规范,其一是逻辑处理要放在Java应用程序端;其二是不允许在DB端写存储过程。其实不难看出,第二条规范是对第一条的加强。存在即合理,更何况是有着各类项目实践经验的理论框架,更应该值得遵守。而问题是如何遵守才是正确的?

 

理论框架只给出了一个指导方法,告诉你普世之下应该这么做,然而并没有告诉你,当遇到这种情况时,如何判断?

 

再回到应用架构规范,到底是哪里出了问题呢?显然是在于开发人员对规范的应用上出了问题,也就是没有理解规范的真正内容及目的。规范是出于扩展及性能考量,要求逻辑处理应放在应用程序服务器上完成,如果一味的生搬硬套,像本案例这样,只能适得其反得不偿失。

 

敏捷之殇:能力之轻,无法承受责任之重

 

事后开发兄弟聊天:

 

“这个当初是谁设计的?”

“是我自己”

“你觉得自己有足够的设计能力吗?”

“没有,我工作不到两年,开发能力还很欠缺,但是没有办法,项目上,大家都很忙,各自负责各自的模块,从需求到交接后,就要承担设计和开发的工作。”

 

看着开发兄弟满脸的焦虑和自责,我也不知道该如何劝导。

 

“没关系,大家都是这么经历过来的。大风大浪才能锻炼出优秀水手不是?”

 

敏捷开发讲究的是快速迭代与交付,因此在开发过程中,往往是重进度而轻质量。在快速交付过程中,对开发人员的要求是极高的。在常规的传统软件开发时,从方案到开发,是要层层评审的,而设计与开发的角色往往是由不同人来承当的。

 

而敏捷开发则不然,为了轻装上阵,快速推进,项目权限向一线授权,开发人员有了很多的权限;另一方面,将评审环节直接砍掉。这样开发人员就具有了生杀大权。

 

权利越大,责任就也越大,也就意味着开发人员要有足够的知识、经验及能力去驾驭。所以,敏捷开发对项目成员的要求是非常高的。理想的要求是能做到自我管理,这是何其高的境界呀,终其一生也难带到这个高度。因为只有这样的人员,才能保质保量的完成敏捷交付。

 

而事实上,在人员技能上,该项目远没有达到要求,这个项目的开发工作全部是由外包承接,刚毕业的应届生充当经验丰富的急先锋,当然,这样能得以成形,也离不开外包商在商务公关上的运作。这种想马儿不吃草,又想马儿跑得快的做法,最终导致了上线不到一年的系统,已经满目疮痍,各种性能问题频发。好在项目组也即时发现了这个问题,即时抢救,大量从外部招聘中断顾问来从事开发工作,不惜血本。

活动预告