触发器异常导致的druid问题

来源:互联网 发布:希捷数据恢复要多少钱 编辑:程序博客网 时间:2024/06/05 20:46

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

  关于什么是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;        }    }

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

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;
  问题解决,曲线救国,不是正经路子。

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


阅读全文
0 0