触发器异常导致的druid问题

        距离上一次写博客,时间已经非常久远了。经历了一段觉得没有东西可以写的时间,本质上应该是失去了监管后的自我放松吧。期间,复写的念头来来走走,都因为懒而作罢。学习这件事情,不能等到用时方恨少……否则变得被动,自我改造中!!!入正题。

  关于什么是druid,这里不做介绍了,有兴趣了解的朋友先自行找些资料哈,sorry。这里我打算只是记录一下我遇到的问题。如果你不曾使用过这个东西,那这里的内容可能看着也没啥意思哈。

  异常现象

  近期的一个用户中心的项目提供对外的登录认证、用户基本信息的调取等接口,给公司的其他平台使用。因此在性能上要求支持300并发,平均响应时间在1s左右。我们采用了druid连接池,为了提高响应速度我们开启了PreparedStatement(pscache)缓存,缓存数量设置在100。在压力测试的时候没有发现什么问题,但是上线后在某种特定情况下,不定期出现服务器卡死的现象。监控了数据库连接数、带宽、cpu、内存、服务器之间的网络连接数等等,均为发现异常情况。

  发现问题

  经过很多次猜测,并实验。发现我们的问题在于启用了pscache,我们关闭了缓存以后响应速度下来了。但是服务器不会再出现卡死现象。因此,基本上锁定问题出在这个pscache上。 进一步测试实验发现,卡死状态下,出现问题的仅有两个功能登录和机构添加。这两个功能都涉及到数据库的触发器。查询数据库操作日志发现卡死时触发器报错,因此锁定在触发器报错时pscache对该错误的处理上。

  查看druid的源码

   跟踪druid连接池的代码,类DruidDataSource和OracleExceptionSorter。

   DruidDataSource类中的该方法决定是否还将使用过的连接归还给连接池,或者作废出错的连接。

public void handleConnectionException(DruidPooledConnection pooledConnection, Throwable t) throws SQLException {
        DruidConnectionHolder holder = pooledConnection.getConnectionHolder();
        this.errorCount.incrementAndGet();
        this.lastError = t;
        this.lastErrorTimeMillis = System.currentTimeMillis();
        if(!(t instanceof SQLException)) {
            throw new SQLException("Error", t);
        } else {
            SQLException sqlEx = (SQLException)t;
            ConnectionEvent event = new ConnectionEvent(pooledConnection, sqlEx);
            Iterator requireDiscard = holder.getConnectionEventListeners().iterator();

            while(requireDiscard.hasNext()) {
                ConnectionEventListener eventListener = (ConnectionEventListener)requireDiscard.next();
                eventListener.connectionErrorOccurred(event);
            }

            if(this.exceptionSorter != null && this.exceptionSorter.isExceptionFatal(sqlEx)) {
                if(pooledConnection.isTraceEnable()) {
                    Map requireDiscard1 = this.activeConnections;
                    synchronized(this.activeConnections) {
                        if(pooledConnection.isTraceEnable()) {
                            this.activeConnections.remove(pooledConnection);
                            pooledConnection.setTraceEnable(false);
                        }
                    }
                }

                boolean requireDiscard2 = false;
                synchronized(pooledConnection) {
                    if(!pooledConnection.isClosed() || !pooledConnection.isDisable()) {
                        holder.setDiscard(true);
                        pooledConnection.disable(t);
                        requireDiscard2 = true;
                    }
                }

                if(requireDiscard2) {
                    this.discardConnection(holder.getConnection());
                    holder.setDiscard(true);
                }

                LOG.error("discard connection", sqlEx);
            }

            throw sqlEx;
        }
    }

   O racleExceptionSorter类中的该方法对数据库返回的错误进行处理,标识连接是否作废:

public boolean isExceptionFatal(SQLException e) {
        int error_code = Math.abs(e.getErrorCode());
        switch(error_code) {
        case 28:
        case 600:
        case 1012:
        case 1014:
        case 1033:
        case 1034:
        case 1035:
        case 1089:
        case 1090:
        case 1092:
        case 1094:
        case 2396:
        case 3106:
        case 3111:
        case 3113:
        case 3114:
        case 3134:
        case 3135:
        case 3136:
        case 3138:
        case 3142:
        case 3143:
        case 3144:
        case 3145:
        case 3149:
        case 6801:
        case 6802:
        case 6805:
        case 9918:
        case 9920:
        case 9921:
        case 17001:
        case 17002:
        case 17008:
        case 17024:
        case 17089:
        case 17401:
        case 17409:
        case 17410:
        case 17416:
        case 17438:
        case 17442:
        case 25407:
        case 25408:
        case 25409:
        case 25425:
        case 29276:
        case 30676:
            return true;
        default:
            if(error_code >= 12100 && error_code <= 12299) {
                return true;
            } else {
                String error_text = e.getMessage().toUpperCase();
                return error_code >= 20000 && error_code < 21000 || !error_text.contains("SOCKET") && !error_text.contains("套接字") && !error_text.contains("CONNECTION HAS ALREADY BEEN CLOSED") && !error_text.contains("BROKEN PIPE") && !error_text.contains("管道已结束")?this.fatalErrorCodes.contains(Integer.valueOf(error_code)):true;
            }
        }
    }
}

   触发器抛出的错误的错误为:java.sql.SQLException:internal error,对应的错误码我不记得了,但是不在上面这个类的所有case项里。druid认为oracle数据库内部错误不数据应用程序的数据库连接有问题,因此该连接被连接池保持了。但实际上该连接在开始pscache的情况下是有问题的,不能复用。具体为啥没有看懂,汗。

        解决办法

        触发器发生错误时,手动抛出错误,抛出一个druid能够识别的错误。在触发器END之前添加如下内容:

EXCEPTION
  WHEN OTHERS THEN
   RAISE NOT_LOGGED_ON;
  问题解决,曲线救国,不是正经路子。

        总结:这种错误实在是不容易发现,得多做实验。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值