gitbook/高楼的性能工程实战课/docs/369897.md

423 lines
27 KiB
Markdown
Raw Permalink Normal View History

2022-09-03 22:05:03 +08:00
# 19 | 生成订单信息之一应用JDBC池优化和内存溢出分析
你好,我是高楼。
在这节课中,我们来看一下生成订单接口的基准场景是什么结果。
你将看到一些重复的问题比如SQL的问题定位虽然具体的问题不同但我们的分析逻辑没有区别我会简单带过。同时你也会看到一些新的问题比如JDBC池增加之后由于数据量过大导致JVM内存被消耗光批量业务和实时业务共存导致的锁问题等。这节课我们重点来看看这样的问题如何进一步优化。
话不多说,开整!
## 场景运行数据
对于生成订单接口,我们第一次试执行性能场景的结果如下:
![](https://static001.geekbang.org/resource/image/dc/6a/dcbed4b3bc53be11f7d18280b8f1d16a.png?wh=1830*831)
从场景执行的结果来看。40个压力线程只跑出来50多的TPS响应时间也蹭蹭蹭地跑了近700ms。这显然是一个非常慢的接口了。
从这样的接口来看,我们选择这个项目算是选择对了,因为到处都是性能问题。
下面我们就来一步步分析一下。
## 架构图
前面我们做过多次描述,画架构图是为了知道分析的路径。所以按照惯例,我们仍然把架构图列在这里。
![](https://static001.geekbang.org/resource/image/89/5f/8973byyed69dec259cb6f7d4bf3f4b5f.png?wh=1215*731)
由于这个接口比较复杂,架构图看起来有点乱,我又整了一个简化版本:
![](https://static001.geekbang.org/resource/image/c9/aa/c95c8daab26ca52fb2df688986a1c0aa.png?wh=894*736)
Order服务是这个接口的核心因此你可以看到我把Order相关的服务都筛选了出来这样我们就能很清楚地知道它连接了哪些东西。
下面我们来拆分响应时间。
## 拆分响应时间
因为在场景运行的时候我们看到响应时间比较长所以我们用APM工具来拆分一下
* Gateway
![](https://static001.geekbang.org/resource/image/b7/5c/b7f6b87b608cc0fc9b8f6e05baf90c5c.png?wh=330*183)
从上图我们就可以看到Gateway上的时间在700ms左右这与前面的场景数据是可以对上的。
我说明一下这张小图的采样间隔是分钟因此你可能会发现这个曲线和压力工具给出的TPS曲线在一些细节上对应不起来。不过这没关系我们更应该关注整体的趋势。
* Order
![](https://static001.geekbang.org/resource/image/c9/01/c9aaa22298a00d7b68a6d89f7cef7501.png?wh=324*161)
我们前面提到Order是生产订单信息这个接口的重点并且它的业务逻辑也非常复杂因此我们要多关注这个服务。
从数据上来看Order的是时间消耗在350毫秒左右占到整个响应时间的一半。这是我们着重要分析的而Gateway的转发能力也是要考虑的问题点只是Gateway上没有逻辑只做转发如果是因为数据量大而导致的Gateway转发慢那我们解决了Order的问题之后Gateway的问题也就会被解决。所以我们先分析Order的问题。
所以,我们现在就来分析一下。
## 第一阶段
### 全局监控分析
我们先看全局监控:
![](https://static001.geekbang.org/resource/image/58/e8/584fc960c184d9c4bba12fae9b6c58e8.png?wh=1826*457)
一眼扫过去啥也没有。既没有明显的CPU资源消耗也没有明显的网络资源、IO资源瓶颈。
**遇到这种情况,我们一定要留意整个链路上有限制的点**。什么是有限制的点?比如说,各种池(连接池、等)、栈中的锁、数据库连接、还有数据库的锁之类。其实,总结下来就是一个关键词:**阻塞**。
我们只要分析出阻塞的点,就能把链路扩宽,进而把资源都用起来。
当然,也有可能在你分析了一圈之后,发现没有任何有阻塞的点,可是资源就是用不上去。这种情况只有一种可能,那就是你分析得还不够细致。因为可能存在阻塞的地方实在太多了,我们只能一步步拆解。
### 定向监控分析
正所谓“心中常备决策树,让你分析不迷路”。到了定向监控分析这里,按照[第4讲](https://time.geekbang.org/column/article/356789)中强调的性能分析决策树我们先来分析Order服务
![](https://static001.geekbang.org/resource/image/3e/84/3e2cd8928022bccf83fe69a71f004784.jpg?wh=2000*1706)
在我分析Order的线程栈信息时发现在Order的栈中有大量这样的内容
```
"http-nio-8086-exec-421" Id=138560 WAITING on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@a268a48
at sun.misc.Unsafe.park(Native Method)
- waiting on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@a268a48
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at com.alibaba.druid.pool.DruidDataSource.takeLast(DruidDataSource.java:1899)
at com.alibaba.druid.pool.DruidDataSource.getConnectionInternal(DruidDataSource.java:1460)
at com.alibaba.druid.pool.DruidDataSource.getConnectionDirect(DruidDataSource.java:1255)
at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:5007)
at com.alibaba.druid.filter.stat.StatFilter.dataSource_getConnection(StatFilter.java:680)
at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:5003)
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1233)
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1225)
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:90)
..........................
at com.dunshan.mall.order.service.impl.PortalOrderServiceImpl$$EnhancerBySpringCGLIB$$f64f6aa2.generateOrder(<generated>)
at com.dunshan.mall.order.controller.PortalOrderController.generateOrder$original$hak2sOst(PortalOrderController.java:48)
at com.dunshan.mall.order.controller.PortalOrderController.generateOrder$original$hak2sOst$accessor$NTnIbuo7(PortalOrderController.java)
at com.dunshan.mall.order.controller.PortalOrderController$auxiliary$MTWkGopH.call(Unknown Source)
..........................
at com.dunshan.mall.order.controller.PortalOrderController.generateOrder(PortalOrderController.java)
..........................
```
你看栈信息中有很多getConnection这明显是Order服务在等数据库连接池。所以我们要做的就是把JDBC池加大
```
原配置:
initial-size: 5 #连接池初始化大小
min-idle: 10 #最小空闲连接数
max-active: 20 #最大连接数
修改为:
initial-size: 20 #连接池初始化大小
min-idle: 10 #最小空闲连接数
max-active: 40 #最大连接数
```
你可以看到我在这里并没有把JDBC池一次性修改得太大主要是因为我不想为了维护连接池而产生过多的CPU消耗。我也建议你在增加资源池的时候先一点点增加看看有没有效果等有了效果后再接着增加。
修改JDBC池后我们再来看一下压力场景的执行数据
![](https://static001.geekbang.org/resource/image/a8/bb/a8e672d9f5f829509a68216ed0146ebb.png?wh=1829*834)
从数据上看TPS有上升的趋势并且一度达到了150以上。可是紧接着TPS就掉下来了这个时候的响应时间倒是没有明显增加。而且你看TPS不仅掉下来了而且还断断续续的极为不稳定。
此外,我们还可以发现,在后续的压力中不仅有错误信息产生,响应时间也在上升。与此同时,我查看了全局监控的资源,并没有发现太大的资源消耗。既然有错误产生,没二话,我们只能整它!
## 第二阶段
### 全局监控分析
因为我们在前面修改了Order的JDBC池所以在出现新的问题之后我们先来看一下Order服务的健康状态。在查看Order服务的top时看到如下信息
```
top - 01:28:17 up 19 days, 11:54, 3 users, load average: 1.14, 1.73, 2.27
Tasks: 316 total, 1 running, 315 sleeping, 0 stopped, 0 zombie
%Cpu0 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 3.0 us, 2.7 sy, 0.0 ni, 93.6 id, 0.0 wa, 0.0 hi, 0.3 si, 0.3 st
%Cpu2 : 3.4 us, 3.4 sy, 0.0 ni, 93.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu3 : 3.7 us, 2.8 sy, 0.0 ni, 93.5 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu4 : 3.6 us, 2.1 sy, 0.0 ni, 93.6 id, 0.0 wa, 0.0 hi, 0.3 si, 0.3 st
%Cpu5 : 2.8 us, 1.8 sy, 0.0 ni, 95.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 16265992 total, 2229060 free, 9794944 used, 4241988 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 6052732 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
29349 root 20 0 8836040 4.3g 16828 S 99.7 27.4 20:51.90 java
1089 root 20 0 2574864 98144 23788 S 6.6 0.6 2066:38 kubelet
```
悲催的数据还是来了你看有一个us cpu达到了100%!这是啥情况?
进入到容器中,我通过 top -Hp和jstack -l 1 两个命令查看进程后发现原来是VM Thread线程占用了CPU这个线程是做垃圾回收GC的。 既然如此那我们就来看一下内存的回收状态查看jstat如下
```
[root@svc-mall-order-7fbdd7b85f-ks828 /]# jstat -gcutil 1 1s
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 93 652.664 681.486
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 93 652.664 681.486
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 93 652.664 681.486
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 93 652.664 681.486
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 93 652.664 681.486
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 93 652.664 681.486
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 93 652.664 681.486
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 94 659.863 688.685
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 94 659.863 688.685
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 94 659.863 688.685
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 94 659.863 688.685
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 94 659.863 688.685
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 94 659.863 688.685
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 94 659.863 688.685
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 95 667.472 696.294
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 95 667.472 696.294
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 95 667.472 696.294
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 95 667.472 696.294
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 95 667.472 696.294
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 95 667.472 696.294
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 95 667.472 696.294
0.00 100.00 100.00 100.00 94.86 93.15 168 28.822 95 667.472 696.294
0.00 100.00 100.00 100.00 94.85 93.14 168 28.822 96 674.816 703.638
0.00 100.00 100.00 100.00 94.85 93.14 168 28.822 96 674.816 703.638
0.00 100.00 100.00 100.00 94.85 93.14 168 28.822 96 674.816 703.638
0.00 100.00 100.00 100.00 94.85 93.14 168 28.822 96 674.816 703.638
0.00 100.00 100.00 100.00 94.85 93.14 168 28.822 96 674.816 703.638
0.00 100.00 100.00 100.00 94.85 93.14 168 28.822 96 674.816 703.638
0.00 100.00 100.00 100.00 94.85 93.14 168 28.822 96 674.816 703.638
```
从上面的数据来看FullGC在不断出现但是又回收不了内存这个问题就严重了。
你要注意,对于这种情况,我们正常的判断逻辑应该是:一**个实时的业务系统就算是有FullGC也应该是每次都回收到正常的状态。如果HEAP内存确实不够用那我们可以增加。但是如果HEAP一直在减少直到FullGC也回收不了那就有问题了。**
因此,对于这样的问题,我们要做两方面的分析:
1. 内存确实在被使用所以FullGC回收不了。
2. 内存有泄露并且已经泄露完所以FullGC无法回收。
那么接下来,我们在做定向监控分析时就要从这两个角度来思考。
### 定向监控分析
既然内存已经满了我们就执行一下jmap -histo:live 1|head -n 50来看看占比相对较多的内存是什么
```
[root@svc-mall-order-7fbdd7b85f-ks828 /]# jmap -histo:live 1|head -n 50
num #instances #bytes class name
----------------------------------------------
1: 74925020 2066475600 [B
2: 2675397 513676056 [[B
3: 2675385 85612320 com.mysql.cj.protocol.a.result.ByteArrayRow
4: 2675386 42806176 com.mysql.cj.protocol.a.MysqlTextValueDecoder
5: 246997 27488016 [C
6: 80322 16243408 [Ljava.lang.Object;
7: 14898 7514784 [Ljava.util.HashMap$Node;
8: 246103 5906472 java.lang.String
9: 109732 3511424 java.util.concurrent.ConcurrentHashMap$Node
10: 37979 3342152 java.lang.reflect.Method
11: 24282 2668712 java.lang.Class
12: 55296 2654208 java.util.HashMap
13: 15623 2489384 [I
14: 81370 1952880 java.util.ArrayList
15: 50199 1204776 org.apache.skywalking.apm.agent.core.context.util.TagValuePair
16: 36548 1169536 java.util.HashMap$Node
17: 566 1161296 [Ljava.util.concurrent.ConcurrentHashMap$Node;
18: 28143 1125720 java.util.LinkedHashMap$Entry
19: 13664 1093120 org.apache.skywalking.apm.agent.core.context.trace.ExitSpan
20: 23071 922840 com.sun.org.apache.xerces.internal.dom.DeferredTextImpl
21: 35578 853872 java.util.LinkedList$Node
22: 15038 842128 java.util.LinkedHashMap
23: 52368 837888 java.lang.Object
24: 17779 711160 com.sun.org.apache.xerces.internal.dom.DeferredAttrImpl
25: 11260 630560 com.sun.org.apache.xerces.internal.dom.DeferredElementImpl
26: 18743 599776 java.util.LinkedList
27: 26100 598888 [Ljava.lang.Class;
28: 22713 545112 org.springframework.core.MethodClassKey
29: 712 532384 [J
30: 6840 492480 org.apache.skywalking.apm.agent.core.context.trace.LocalSpan
31: 6043 483440 org.apache.skywalking.apm.dependencies.net.bytebuddy.pool.TypePool$Default$LazyTypeDescription$MethodToken
32: 7347 352656 org.aspectj.weaver.reflect.ShadowMatchImpl
33: 6195 297360 org.springframework.core.ResolvableType
34: 6249 271152 [Ljava.lang.String;
35: 11260 270240 com.sun.org.apache.xerces.internal.dom.AttributeMap
36: 3234 258720 java.lang.reflect.Constructor
37: 390 255840 org.apache.skywalking.apm.dependencies.io.netty.util.internal.shaded.org.jctools.queues.MpscArrayQueue
38: 7347 235104 org.aspectj.weaver.patterns.ExposedState
39: 5707 228280 java.lang.ref.SoftReference
40: 3009 216648 org.apache.skywalking.apm.agent.core.context.TracingContext
41: 13302 212832 org.apache.ibatis.scripting.xmltags.StaticTextSqlNode
42: 8477 203448 org.apache.skywalking.apm.dependencies.net.bytebuddy.pool.TypePool$Default$LazyTypeDescription$MethodToken$ParameterToken
43: 5068 162176 java.util.concurrent.locks.ReentrantLock$NonfairSync
44: 2995 143760 org.apache.skywalking.apm.agent.core.context.trace.TraceSegmentRef
45: 2426 135856 java.lang.invoke.MemberName
46: 3262 130480 java.util.WeakHashMap$Entry
47: 1630 130400 org.apache.skywalking.apm.agent.core.context.trace.EntrySpan
[root@svc-mall-order-7fbdd7b85f-ks828 /]#
```
在分析内存时我们可以过滤掉java自己的对象只看和业务相关的对象。从上面的第3、4条可以看出com.mysql.cj.protocol和SQL相关那我们就到innodb\_trx表中去查一下看看有没有执行时间比较长的SQL。
在查询过程中我们看到了这样一条SQL
```
select id, member_id, coupon_id, order_sn, create_time, member_username, total_amount pay_amount, freight_amount, promotion_amount, integration_amount, coupon_amount discount_amount, pay_type, source_type, status, order_type, delivery_company, delivery_sn auto_confirm_day, integration, growth, promotion_info, bill_type, bill_header, bill_content bill_receiver_phone, bill_receiver_email, receiver_name, receiver_phone, receiver_post_code receiver_province, receiver_city, receiver_region, receiver_detail_address, note, confirm_status delete_status, use_integration, payment_time, delivery_time, receive_time, comment_time modify_time from oms_order WHERE ( id = 0 and status = 0 and delete_status = 0 )
```
进而我又查询了这个语句发现涉及到的数据有4358761条这显然是代码写的有问题。那我们就去查看一下在代码中哪里调用了这个SQL。
通过查看代码,看到如下逻辑:
```
example.createCriteria().andIdEqualTo(orderId).andStatusEqualTo(0).andDeleteStatusEqualTo(0);
List<OmsOrder> cancelOrderList = orderMapper.selectByExample(example);
```
这段代码对应的select语句是
```
<select id="selectByExample" parameterType="com.dunshan.mall.model.OmsOrderExample" resultMap="BaseResultMap">
select
<if test="distinct">
distinct
</if>
<include refid="Base_Column_List" />
from oms_order
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
<if test="orderByClause != null">
order by ${orderByClause}
</if>
</select>
```
这是一个典型的语句没过滤的问题。像这样的开发项目也最多就是做个Demo用。要是在真实的线上项目中早就不知道伤害了多少人。
我们在这里直接修改代码加上limit不让它一次性查询出所有的数据。
然后,我们看一下优化效果:
![](https://static001.geekbang.org/resource/image/42/5c/42d0c383e03acf38b31e4fc7879c2f5c.png?wh=1830*829)
你看没有出现TPS断裂的情况了优化效果还是有的说明那条SQL语句不会再查出太多数据把内存给占满了。
不过TPS值并没有增加多少所以我们必须做第三阶段的分析。
## 第三阶段
这次我们不从全局监控数据来看了,有了前面的经验,我们直接来做定向监控分析。
### 定向监控分析
因为我们在前面改了SQL所以在执行SQL之后我们要去查一下innodb\_trx表看看还有没有慢的SQL。 结果看到了如下SQL
![](https://static001.geekbang.org/resource/image/cb/73/cb7cd20de10b8d55b3c94f47c2576273.png?wh=1889*222)
把这个SQL拿出来看看它的执行计划
![](https://static001.geekbang.org/resource/image/67/54/67785136ea6b3748d5ab7c148445ff54.png?wh=1016*63)
又是一个典型的全表扫描并且是由一个update使用的。看到这里你是不是有种想把开发拉出去祭旗的冲动
由于生成订单信息是一个复杂的接口我们不急着收拾这个SQL先把slow log全都拿出来分析一遍。
请你注意有时候项目执行的场景多了数据相互之间的影响就会很大容易导致我们分析的方向不准确。所以我们最好把slow log都清一遍。反正我通常都会这么干因为我不想看到乱七八糟的数据。
在清理完慢SQL、重新执行场景之后我又把slow log拿出来用pt-digest-query分析了一遍关于这一点我们在[第16讲](https://time.geekbang.org/column/article/367285)中讲过,如果你不记得的话,建议你再回顾一下),看到如下的数据:
```
# Profile
# Rank Query ID Response time Calls R/Call V/M I
# ==== ============================ =============== ===== ======== ===== =
# 1 0x2D9130DB1449730048AA1B5... 1233.4054 70.5% 3 411.1351 2.73 UPDATE oms_order
# 2 0x68BC6C5F4E7FFFC7D17693A... 166.3178 9.5% 2677 0.0621 0.60 INSERT oms_order
# 3 0xB86E9CC7B0BA539BD447915... 91.3860 5.2% 1579 0.0579 0.01 SELECT ums_member
# 4 0x3135E50F729D62260977E0D... 61.9424 3.5% 4 15.4856 0.30 SELECT oms_order
# 5 0xAE72367CD45AD907195B3A2... 59.6041 3.4% 3 19.8680 0.13 SELECT oms_order
# 6 0x695C8FFDF15096AAE9DBFE2... 49.1613 2.8% 1237 0.0397 0.01 SELECT ums_member_receive_address
# 7 0xD732B16862C1BC710680BB9... 25.5382 1.5% 471 0.0542 0.01 SELECT oms_cart_item
# MISC 0xMISC 63.2937 3.6% 1795 0.0353 0.0 <9 ITEMS>
```
通过上面的Profile信息我们看到第一个语句消耗了总时间的70.5%第二个语句消耗了总时间的9.5%。我们说要解决性能问题其实解决的就是这种消耗时间长的语句。而后面的SQL执行时间短我们可以暂时不管。
通常在这种情况下你可以只解决第一个语句然后再回归测试看看效果再来决定是否解决第二个问题。我先把这两个完整的SQL语句列在这里
```
1. UPDATE oms_order SET member_id = 260869, order_sn = '202102030100205526', create_time = '2021-02-03 01:05:56.0', member_username = '7dcmppdtest15176472465', total_amount = 0.00, pay_amount = 0.00, freight_amount = 0.00, promotion_amount = 0.00, integration_amount = 0.00, coupon_amount = 0.00, discount_amount = 0.00, pay_type = 0, source_type = 1, STATUS = 4, order_type = 0, auto_confirm_day = 15, integration = 0, growth = 0, promotion_info = '', receiver_name = '6mtf3', receiver_phone = '18551479920', receiver_post_code = '66343', receiver_province = '北京', receiver_city = '7dGruop性能实战', receiver_region = '7dGruop性能实战区', receiver_detail_address = '3d16z吉地12号', confirm_status = 0, delete_status = 0 WHERE id = 0;
2. insert into oms_order (member_id, coupon_id, order_sn, create_time, member_username, total_amount, pay_amount, freight_amount, promotion_amount, integration_amount, coupon_amount, discount_amount, pay_type, source_type, status, order_type, delivery_company, delivery_sn, auto_confirm_day, integration, growth, promotion_info, bill_type, bill_header, bill_content, bill_receiver_phone, bill_receiver_email, receiver_name, receiver_phone, receiver_post_code, receiver_province, receiver_city, receiver_region, receiver_detail_address, note, confirm_status, delete_status, use_integration, payment_time, delivery_time, receive_time, comment_time, modify_time)values (391265, null, '202102030100181755', '2021-02-03 01:01:03.741', '7dcmpdtest17793405657', 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, null, null, 15, 0, 0, '', null, null, null, null, null, 'belod', '15618648303', '93253', '北京', '7dGruop性能实战', '7dGruop性能实战区', 'hc9r1吉地12号', null, 0, 0, null, null, null, null, null, null);
```
我们先来看第一个语句。这个update语句虽然被调用的次数不多但是特别慢。这显然不应该是实时接口在调用那我们就要查一下到底是什么业务调用了这个语句。你看在这个语句中update更新的是where条件中ID为0的数据这看上去就是一个批量业务。
我们再来看第二个语句。第二个insert语句调用次数多应该是实时交易的SQL。通常我们会通过批量插入数据来优化insert所以就需要调整bulk\_insert\_buffer\_size参数默认是8M来实现这一点。因为bulk\_insert\_buffer\_size就是在批量插入数据时提高效率的。我去查询了一下这个参数确实没有优化过还是默认值。
这里你要注意一点在生产环境中因为Order表中要加索引所以在架构设计时也最好是主从分离让update、insert和select不会相互影响。
分析完这两个SQL语句我们先来查找第一个SQL的来源。通过查找代码可以看到这里调用了该语句
```
orderMapper.updateByPrimaryKeySelective(cancelOrder);
```
但是请注意这个updateByPrimaryKeySelective方法是批量任务中的而批量任务应该和实时交易分开才是。如果你是作为性能团队的人给架构或开发提优化建议那你可以这样给建议
1. 读写分离;
2. 批量业务和实时业务分离。
在这里,我先把这个批量业务给分离开,并且也不去调用它。但是,在真实的业务逻辑中,你可不能这么干。我之所以这么做,是为了看后续性能优化的效果和方向。
做了上述修改之后TPS如下
![](https://static001.geekbang.org/resource/image/20/3d/20e420c49edcd201cc68c0698e7ddf3d.png?wh=1836*836)
从效果上来看TPS能达到300左右了响应时间看起来也稳定了。我们终于可以进入正常的性能分析阶段了哈哈。
不过到这里我们的工作并没有结束从上图来看TPS在300左右根据我们的整体系统资源来考虑这个TPS还是偏低的所以这个接口显然还有优化的空间。所以在下节课中我们接着来唠。
## 总结
在这节课中,我们做了三个阶段的分析优化。
在第一阶段中我们修改了JDBC池虽然TPS有上升的趋势但是新问题也同样出现了TPS非常不稳定还有断断续续的情况。
在第二阶段中我们分析了内存溢出的问题定位出了原因并优化了内存问题。虽然我们在TPS曲线上明显看到了优化的效果但仍然没有达到理想的程度。
在第三阶段中我们分析定位了SQL的问题这是非常合乎逻辑的。因为我们在第二阶段中修改了SQL所以到了第三阶段就要直接到数据库中做相应的定位。从结果上看效果不错TPS已经有明显正常的趋势了。不过你要注意的是当批量业务和实时业务同时出现在同一个数据库中并且是对同样的表进行操作这时你就得考虑一下架构设计是否合理了。
总之在这节课中你可以看到当SQL查询出来的数据到了应用内存的时候导致了内存的增加。而应用内存的增加也增加了GC的次数进而消耗了更多的CPU资源。
## 课后作业
最后,请你思考两个问题:
1. 为什么JDK中看到VM Thread线程消耗CPU高会去查看内存消耗是否合理
2. 在MySQL中分析SQL问题时为什么要先查询innodb\_trx表
记得在留言区和我讨论、交流你的想法,每一次思考都会让你更进一步。
如果你读完这篇文章有所收获,也欢迎你分享给你的朋友,共同学习进步。我们下一讲再见!