作者 | Christine Qu
编辑 | Benny
上一期主要介绍了业界应用最普遍的一种软件架构:分层架构,并以反偷税系统的增值税稽查为例着重展开了分层架构的性能分析。上一期很多读者读完后觉得不过瘾,想接着看,今天赶紧为各位呈上新一期文章。在这一期文章中将深入性能测试分析环节,干货满满。
相关阅读:大咖专栏 | 要分层架构,还是要省钱+耍帅?(一)
接下来我们进行三组测试,对分层架构的不同配置情况以及非分层架构的性能进行分析比较。在每组测试中,测试数据是税务局收到的5天的买卖记录,即5组文件,每组2个。每个文件,或者说每天的买卖记录的文件,都分别是70万行左右的数据量。
1
第一组测试
第一组测试比较以下四种情况:
ADF 1 Thread - 多层架构,使用OracleADF框架,单线程执行
ADF 4 Threads - 多层架构,使用OracleADF框架,4个线程同时处理数据
ADF 16 Threads - 多层架构,使用OracleADF框架,16个线程同时处理数据
PL/SQL 1 Thread - 使用PL/SQL编程,单线程执行
测试结果如下:
ADF 16 Threads这种方法的用时最短(802秒)。ADF 1Thread这种方法的用时最长(8558秒)。
有意思的是,同是单线程执行,PL/SQL 1 Thread这种方法的用时只有1727秒,远远低于ADF 1 Thread这种方法的8558秒。
再比较一下每种方法每秒钟处理的数据量。
ADF 16 Threads这种方法每秒钟处理的数据量最多(4711行/秒)。ADF 1 Thread这种方法每秒钟处理的数据量最少(441行/秒)。
同是单线程执行,PL/SQL 1 Thread这种方法每秒钟处理的数据量是2188行,远远高于ADF 1 Thread这种方法的441行/秒。
同是单线程执行,为什么PL/SQL 1 Thread的性能会远远好于ADF 1 Thread呢?AWR报告给到我们启示:
从上面的AWR报告里面可以看出,ADF 1 Thread的每秒DB Time只有0.4,而PL/SQL 1 Thread的每秒DB Time是1.0。DB Time是用户进程在数据库里面活动的时间,包括CPU时间(即DB CPU)和非空闲等待的时间。这也就是说,ADF 1 Thread这种方法并不能让数据库服务器上为之服务的那个进程全负荷的工作起来,而PL/SQL 1 Thread则能够。这也就是说,ADF 1 Thread这种方法在数据库以外的开销可不是一般的大啊!
接下来我们再来看一下ADF 16 Thread这种方法的情况。ADF 16 Thread这种方法,数据库上是不是应该有16个相应的进程为之服务,每秒DB Time是不是应该是16呢?一看下面的AWR报告便知:
只有5.7!你以为你的并发度是16,那真的只是你以为啊!
总之呢,从这组测试我们可以看出,用PL/SQL也可以轻松实现一行一行处理数据的方法,且性能表现大大优异于ADF这样的分层架构。
顺便提一下,我们这里说的一行一行处理数据的方法,在AWR报告中很容易鉴别:
上图的AWR报告中,每个语句的执行次数都是几千几万次或者几百万次,而每次执行处理的行数(Rows per Exec)都是1行,我们可以由此基本判断,应用程序用的是一行一行处理数据的方法。
分析到此,我们要思考的一个问题是,ADF 16 Threads这种方法目前执行结果最佳,那么,若对系统性能有进一步要求的话,要从何下手?SQL优化会有效果吗?
要想回答这个问题,还是要从AWR里面找线索:
上图的AWR报告中,每条SQL语句的执行时间(Elapsed Time per Exec(s))是0.00。当然,实际上每条SQL语句的执行时间肯定不是0秒,这个0.00是一个舍入的结果。但是这结果确实说明了,每条SQL语句的执行时间都已经很短了,再去做SQL优化,还能优化到哪去呢?
2
第二组测试
第二组测试比较以下四种情况:
ADF 16 Thread - 多层架构,使用OracleADF框架,16个线程同时处理数据
PL/SQL 1 Thread Array size 256 -使用PL/SQL编程,单线程执行,array size设为256
PL/SQL 16 Thread Array size 1 -使用PL/SQL编程,16个线程同时处理数据,array size设为1
PL/SQL 16 Thread Array size 256- 使用PL/SQL编程,16个线程同时处理数据,array size设为256
测试结果如下:
PL/SQL 16 Thread Array size 256这种方法的用时最短(131秒)。PL/SQL 1Thread Array size 256这种方法的用时最长(811秒)。
同是16个线程同时处理数据,array size设为1,PL/SQL 16 Thread Array size 1这种方法的用时只有178秒,远远低于ADF 16Thread这种方法的802秒。
再比较一下每种方法每秒钟处理的数据量。
PL/SQL 16 Thread Array size 256这种方法每秒钟处理的数据量最多(28840行/秒)。PL/SQL 1 Thread Array size 256这种方法每秒钟处理的数据量最少(4659行/秒)。
同是16个线程同时处理数据,array size设为1,PL/SQL 16 Thread Array size 1这种方法每秒钟处理的数据量是21225行,远远高于ADF 16Thread这种方法的4659行/秒。
同是16个线程同时处理数据,array size设为1,为什么PL/SQL 16 Thread Array size 1的性能会远远好于ADF 16 Thread呢?
比较一下他们的AWR报告:
我们又发现了一个蛋疼的事实:完成同样的工作量,ADF 16 Thread所花的DB CPU是4608.1秒,而PL/SQL 16 Thread Array size 1所花的DB CPU只有2351.2秒。大家知道,买OracleDatabase License的时候,通常都是按CPU算钱的,DB CPU一定是地球上最贵的CPU吧。可是可是,ADF 16 Thread性能表现差,却花了更多的DB CPU更多的银子!
让我们冷静的分析一下:
上面这个图说明了一行一行处理数据的话,一条数据用ADF的方法处理和用PL/SQL的方法处理时所需要经过的路径。我们可以看出,用ADF的方法,这个路径非常的长,而PL/SQL的处理引擎就在数据库里面,更接近于SQL引擎,因此用PL/SQL的方法这个路径就短很多。用PL/SQL处理数据,实际上,就是在更靠近数据的地方去处理数据,减少了将数据移进移出数据库的开销。
接下来,我们再进一步分析一下用ADF和PL/SQL方法处理数据所用到的所有的call stack。
下图是ADF Oracle call stack的flame graph。我们可以看到,由于ADF代码是在数据库之外执行的的,因而处理一条数据,除了要与OPI(Oracle Program Interface)/SQL引擎打交道进行Deletes,Inserts,Executes& fetches等等操作,还需要在应用程序与数据库之间有一个交互,即有一个将数据移进移出数据库的过程。
也就是说,ADF程序和PL/SQL程序的call stack在数据库的OPI/SQL引擎里面的部分是一样的,在数据库外面的部分是不同的。
那么问题来了,ADF程序与PL/SQL程序在OPI/SQL引擎里面的call stack是一样的,执行这些call stack所消耗的CPU也是一样的吗?那是当然的吧?!
下图分别是执行ADF 程序和PL/SQL程序在数据库里面的call stack所用到的CPU的比较。您没看错,同样的call stack同样的硬件环境执行起来所消耗的CPU是不一样的!!!而这段相同的call stack只是被不同上层应用程序调用而已,ADF还是PL/SQL。
让我们进一步来看一下用ADF程序还是PL/SQL程序调用这些call stack对于CPU的影响。
从下图我们可以看出,ADF程序执行同样的那些call stack,用了比PL/SQL程序多2倍的CPU指令,用了比PL/SQL程序多1倍的CPU cycle,cache和branch的未命中率远远高于PL/SQL程序。
看到这些数据之前,大家肯定都能够想象,将数据移进移出数据库处理是有代价的,可是有谁能想到,这代价,不仅仅在于移进移出那个过程,连在数据库里面执行同样call stack的CPU使用率都会相差几倍!而CPU的使用率差几倍,那可是意味着数据库服务器上的CPU使用率差几倍,那是带着license的昂贵的CPU啊!
这组测试的结果给到我们的另外一个启示是,同是PL/SQL代码,是否使用array interface对于性能的影响也是相当之大。下图是PL/SQL 1 Thread Array size 1和PL/SQL 1 ThreadArray size 256的AWR报告的比较。我们可以看到,使用array interface可以节省DB CPU,即省钱。
3
第三组测试
第三组测试比较以下四种情况:
ADF 16 Thread - 多层架构,使用OracleADF框架,16个线程同时处理数据
PL/SQL 16 Thread Array size 1 -使用PL/SQL编程,单线程执行,array size设为1
PL/SQL 16 Thread Array size 256- 使用PL/SQL编程,16个线程同时处理数据,array size设为256
Set Based SQL DoP=8 - 使用Set BasedSQL实现业务逻辑,Set Based SQL 执行的并行度为8
测试结果如下:
Set Based SQL DoP=8这种方法的用时最短(14秒)。ADF 16Thread这种方法的用时最长(811秒)。
再比较一下每种方法每秒钟处理的数据量。
Set Based SQL DoP=8这种方法每秒钟处理的数据量最多(169861行/秒)。ADF 16 Thread这种方法每秒钟处理的数据量最少(4711行/秒)。
我们将上面所有测试过的方法处理完所有数据所使用的CPU时间汇总一下:
上图中,蓝色部分表示该方法花在应用层的CPU时间,红色部分表示该方法花在数据库层的CPU时间。我们可以看到Set Based SQL DoP=8这种方法处理完所有数据所花的CPU时间最短,远远低于其他所有方法。
重要的事情说三遍,DB CPU是好贵好贵好贵的CPU,用少量CPU完成同样的工作量,那就是省钱啊!Set Based SQL DoP=8这种方法是个省钱的好方法吧?!
另外,如果使用Set Based SQL DoP=8这种方法,那么可以通过控制Set Based SQL执行的并行度,来控制Set Based SQL的系统资源使用量从而控制执行时间。能够控制一个任务的执行时间,那必须是耍帅的好方法吧。
再细化一下每种方法处理完所有数据所使用的CPU:
Set Based SQL DoP=8这种方法的省钱耍帅优势一目了然。Set Based SQL DoP=8这个方法这么好,就让我们来说说Set Based processing的那些事。Set based processing,顾名思义,是基于(based)集合(set)的处理(processing)方法。一些特定的SQL操作,比如CREATETABLE AS SELECT, INSERT /*+ APPEND */ SELECT, INTERSECT, MINUS, EXISTS, NOTEXISTS, window functions, multi-table inserts, outer joins等等,是set based的操作。应用诸如此类操作的SQL即是Set BasedSQL。
SQL是用来操作数据库里的数据的,那么使用SetBased SQL来完成业务逻辑,最直接的好处当然是,在最接近数据的地方完成了对数据的处理,因而额外开销最小。另外Oracle数据库对于Set Based SQL有一套完善的优化机制,比如HASH JOIN,比如并行查询并行DML,比如Oracle的Engineered System中CPU和IO的均衡能力等等,使得处理大量数据时,Set Based SQL的执行效率非常高。
是的,没错,数据库不仅仅是一个可以存放数据的地方,也是一个可以处理数据的地方!而且,在数据库里面处理数据还有如上所述的诸多好处。处理速度快,使用的CPU少,容易编码和维护,可以通过控制并行度来控制Set Based SQL的系统资源使用量和执行时间,毫不夸张的说,Set based processing真是省钱耍帅的好帮手啊!
下一期文章我们继续“省钱耍帅”。