mybatis事务路径引发的“血案”

1. 超时情况出现

这事还得从几个星期前说起,某天同事说项目的数据库连接有些问题。项目跑着跑着就会出现Communications link failure,The last packet successfully received from the server was * millisecond ago.The last packet successfully sent to the server was * millisecond ago这种错。

这个之前项目也有类似的情况,我上一篇博文也有说明。MySQL服务器默认的空闲时间超过8个小时(如果你的应用接的是VIP或者MYCAT,则以VIP或者MYCAT的空闲超时时间为准),mysql将自动断开该连接,而连接池却认为该连接还是有效的,当应用申请使用该连接时,就会报以上错。

我们这里的架构是:jdbc–>HA–>Mycat–>MySql

所以如果HA对连接的空闲时间设置为10分钟自动断开连接的话,而且jdbc的空闲连接为10分钟以上,就会出现这个报错。
比如:jdbc(> 10min)–>HA(=10min)

解决方案:

要么将HA的改大,要么将jdbc的改小(我们项目用的是BoneCP连接池,于是修改idleMaxAge为5min),初步观察又正常了。

2. 问题重现

这事过了一两天后,同事忽然又找我说,项目跑着跑着,会卡死(卡死的代码位置为向数据库插入数据)。然后30min后,程序抛出第一点所说的异常,服务又能继续正常运行了。

当时第一反应,是不是idleMaxAge不生效,30min又是怎么来的?当时考虑说是Mycat设置的空闲超时时间就是30min,会不会就是因为这个导致连接中断。(上面说我们的项目是连的HAProxy(10min),后面我们因为调试需要,直连了mycat(30min))

于是DBA将mycat的空闲连接超时时间改为了1h后,重新运行项目,发现依然会卡死30min。可见并非服务端的时间所影响,也许更多原因在连接池上。

3. 更换连接池

因为上面分析可能是连接池,于是考虑更新连接池。
原来我们用的boneCP,于是改为druid。

boneCP核心参数配置

<property name="idleMaxAge" value="5"/>

druid核心参数配置

<property name="timeBetweenEvictionRunsMillis" value="600000"/>
<property name="minEvictableIdleTimeMillis" value="360000"/>
<property name="testWhileIdle" value="true"/>

timeBetweenEvictionRunsMillis:
(1) Destory线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis,则关闭物理连接。
(2)testWhileIdle的判断依据。

minEvictableIdleTimeMillis:把空闲时间超过minEvictableIdleTimeMillis毫秒的连接断开, 直到连接池中的连接数到minIdle为止 连接池中连接可空闲的时间,毫秒。

testWhileIdle:
申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。

其实可以看到,设置上都没有什么问题的。实在无法解析30min分钟的卡死现象。(如果有经验的朋友可以给点建议,谢谢)

4、无意的尝试

这事又过几天,同事跟我说,去掉事务后,程序正常了。于是重新梳理了业务逻辑。如下:
这里写图片描述

1、服务A就是卡死30分钟。
2、去掉的是服务B,服务C的事务。可见他们操作的是同一张表,也有可能操作该表的同一条记录。
3、服务B\C的update的语句是update xxx where id=? and phoneNum=?

于是考虑,会不会因为服务B、服务C的事务导到锁表;以致于服务A无法插入数据。但是innoDB使用的行锁,怎么会引起锁表。

百度得知:

字段没有索引,即使使用wehre条件也会进行表级锁
如果有索引,会锁定对应where条件中索引值的所有行,可理解为对该索引值进行了索引

观察自己的sql语句,id是一个主键(primary key)[即是有主键],在表里不存在多条记录。即使有多条记录,也只是部分行而已,怎么会导致锁表?最多是行锁(此时锁表的结论还只是一个假设)

5. 请教专家

实在没法,只好请教公司里的DBA主管。
她说:

服务B/C里的 update事务太多,高并发下会产生堆积,会产生和表锁一样的效果。从而导到insert不进去。

先不去验证这个说法的正确性,可同事说服务B/C的update事务很少,只给service的包添加了事务,不存在高并发的情况,而且只开了5个线程,同一时间只会有5个update语句在执行(一台服务器)。

6. 重新梳理项目配置

于是确认一下同事说的是不是真实的情况,看了一下spring-mybatis的配置如下:

<tx:advice id="mybatis-txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*find*" read-only="true" />
            <tx:method name="*get*" read-only="true" />
            <tx:method name="*load*" read-only="true" />
            <tx:method name="*check*" read-only="true" />
            <tx:method name="*save*" propagation="REQUIRED"
                rollback-for="java.lang.Exception" />
            <tx:method name="*input*" propagation="REQUIRED"
                rollback-for="java.lang.Exception" />
            <tx:method name="*insert*" propagation="REQUIRED"
                rollback-for="java.lang.Exception" />
            <tx:method name="*add*" propagation="REQUIRED"
                rollback-for="java.lang.Exception" />
            <tx:method name="*update*" propagation="REQUIRED"
                rollback-for="java.lang.Exception" />
            <tx:method name="*delete*" propagation="REQUIRED"
                rollback-for="java.lang.Exception" />
            <tx:method name="*batch*" propagation="REQUIRED"
                rollback-for="java.lang.Exception" />
            <tx:method name="*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
        </tx:attributes>
    </tx:advice>

    <aop:config proxy-target-class="true">
        <!-- Service类的public方法 -->
        <!-- <aop:pointcut id="serviceHandle" expression="(execution(* com.cccc.einvoice.*.service..*.*(..))) 
            or (execution(* com.cccc.einvoice.api.service..*.*(..)))" /> -->
        <aop:pointcut id="serviceHandle"
            expression="(execution(* com.cccc.einvoice.*.*(..)))" />
        <aop:advisor advice-ref="mybatis-txAdvice" pointcut-ref="serviceHandle" />
    </aop:config>

看到aop的扫描路径,同事说的事务只对service生效的代码被屏蔽,换之的是更大范围的包扫描。而刚好mybatis的Mapper也包含在里面,那相当所有的操作都会包括事务,形同高并发。

—–结—–
1、注意事务的aop扫描路径。

—- 未解决问题—-
1、卡死的30min到底是哪里影响?

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值