数据库未关闭导致的故障分析

时间:2022-02-03 00:44:17

简要介绍

在看代码前要必要了解一下java中的值传递和引用传递区别。

值传递:方法调用时,实际参数把它的值传递给对应的形式参数,方法执行中形式参数值的改变不影响实际参 数的值。
引用传递:也称为传地址。方法调用时,实际参数的引用(地址,而不是参数的值)被传递给方法中相对应的形式参数,在方法执行中,对形式参数的操作实际上就是对实际参数的操作,方法执行中形式参数值的改变将会影响实际参数的值。

public static void mergeHotelTree() {
    long start = System.currentTimeMillis();
    SpringContextAdaptor context = QunarServer.getService("SpringAdaptor");
    LinkageClient client = context.getBean("linkageClient");
    HotelIterator<Hotel> hotelIter = null;
    EasyConnection con = null;
    try {
        hotelIter = client.getLinkageInrement(); //第一次调用获取数据
     List<Hotel> hotelList = new ArrayList<Hotel>();
        int count = 0;
        while (hotelIter.hasNext()) {
            Hotel hotel = hotelIter.next();
            hotelList.add(hotel);
            count++;
            if(count % 100 == 0) {
                Map<String, String> wrapperhotelEnableMap = getWHEnableAfterSendProcReq(hotelList); //发送proc请求,返回enable数据集
             mergeHotelTreeHandleDB(con, hotelList, wrapperhotelEnableMap);   //入库操作
 
                hotelList.clear();  //处理完了,最后清空数据
         }
        }
 
        if( hotelList != null && hotelList.size() > 0 ){
            Map<String, String> wrapperhotelEnableMap = getWHEnableAfterSendProcReq(hotelList); //发送proc请求,返回enable数据集
         mergeHotelTreeHandleDB(con, hotelList, wrapperhotelEnableMap);     //入库操作
     }
 
    } catch (Exception e) {
        if (hotelIter != null) {
            hotelIter.rollBack();
        }
        QMonitor.recordOne("mergeHotelTree_fail",System.currentTimeMillis()-start);
        e.printStackTrace();
    } finally {
        if (hotelIter != null) {
            try {
                hotelIter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try {
            if (con != null)
                con.close();
        } catch (java.sql.SQLException e) {
            e.printStackTrace();
        }
    }
}
 
 
public static void mergeHotelTreeHandleDB(EasyConnection con, List<Hotel> hotelList, Map<String, String> wrapperhotelEnableMap) throws Exception {
    int count = 0;
    for (Hotel hotel: hotelList ) {
        String hotelSeq = null;
        long mergeStart = System.currentTimeMillis();
        if (con == null || con.isClosed())
            con = ConnectionManager.currentManager().getConnection(
                    "hotelbase");
 
        String cityCode = getCityFromHotel(hotel.getHotelSeq());
        int affectRow = 0;
        con.setAutoCommit(false);
        ......大量DML操作
        con.commit();
        if ((++count) % 100 == 0)
            con.close();
        QMonitor.recordOne("mergeHotelTree_succ", System.currentTimeMillis()-mergeStart);
    }
}


代码逻辑问题分析

此处代码逻辑要处理的需求如下。

1.sync从qhotel系统可能一次获取hotel数据量过大,sync需要调用proc接口同步数据,但是大小有限制,所以获取的hotel列表需要分多次同步proc系统,每次同步100条hotel信息。

2.mergeHotelTree方法获取hotel信息列表,每循环迭代100次hotel,做一次DML操作。

3.目前代码问题是每循环迭代100次hotel才con.close()释放连接资源,如果最后一次循环迭代次数<100,mergeHotelTree方法是不会做close()的,而且如果出现DML操作异常,也是不能正常释放连接资源。

方法调用con内存结构分析说明(地址值纯属虚构)

上述代码中实际参数con,其引用对象为null,实际在内存中表现是没有任何有效地址空间,在做形式参数传递过程中做了参数拷贝,con变量地址发生转移, 此时实际参数与形式参数的con栈对象地址不同,调用getConnection方法后所指向的堆内存也就不同。

请看如下图所示:

  数据库未关闭导致的故障分析
说明:
1.每当程序调用一个方法时,都会创建自己独立的栈内存(空间),调用完成后,会退栈。

2.mergeHotelTree方法中con对象指向地址为0x0,mergeHotelTreeHandleDB方法con对象,一直操作自己 的栈内存,与mergeHotelTree方法的栈内存无关,mergeHotelTree的con指向地址没有任何变化还是0x0。

代码修改

方案1.

mergeHotelTreeHandleDB方法中,修改关闭连接对象判断代码:if ((++count) % hotelList.size() == 0),但这里有个问题,DML操作异常,也是不能正常释放连接资源。

方案2.

public static void mergeHotelTreeHandleDB(List<Hotel> hotelList, Map<String, String> wrapperhotelEnableMap) throws Exception {
    EasyConnection con = null;
    try {
        con = ConnectionManager.currentManager().getConnection(
                "hotelbase");
        for (Hotel hotel: hotelList ) {
            String hotelSeq = null;
            long mergeStart = System.currentTimeMillis();
 
            String cityCode = getCityFromHotel(hotel.getHotelSeq());
            int affectRow = 0;
            con.setAutoCommit(false);
            大量DML操作
            con.commit();
            QMonitor.recordOne("mergeHotelTree_succ", System.currentTimeMillis()-mergeStart);
        }
    } catch (Exception e) {
        logger.log(Level.SEVERE, e.getMessage(), e);
        throw e;
    } finally {
        try {
            if (con != null)
                con.close();
        } catch (java.sql.SQLException e) {
            e.printStackTrace();
        }
    }
}

方案3:

如果是c/c+代码,用指针来修改原版代码,也是没有问题的。对于一个写了几年c/c+的工程师来说,如果不知道底层内存结构和分配,是寸步难行的。
代码如下:

static void HotelMergeQHotel::mergeHotelTree() {
    .......省略
    std::Vector<Hotel> hotelIter = NULL;
    EasyConnection *con = NULL;
 
    .......省略
    std::Vector<Hotel> hotelList = new Vector<Hotel>();  // stl Vector
 int count = 0;
    while (循环迭代) {
        hotelList.push_back(hotel);  //具体代码省略处理
     count++;
        if(count % 100 == 0) {
                        .......省略
            mergeHotelTreeHandleDB(&con, hotelList, wrapperhotelEnableMap);   //入库操作
         .......省略
        }
    }
 
    if( hotelList != null && hotelList.size() > 0 ){
        .......省略
        mergeHotelTreeHandleDB(&con, hotelList, wrapperhotelEnableMap);     //入库操作
 }
 
 
    if( con != NULL)
       con->close();
 
}
 
 
static void HotelMergeQHotel::mergeHotelTreeHandleDB(EasyConnection **con, std::Vector<Hotel> hotelList, .......省略) throws Exception {
    int count = 0;
    for (Hotel hotel: hotelList ) {
        if ((*con) == NULL || (*con)->isClosed())
            *con = ConnectionManager.currentManager().getConnection(
                    "hotelbase");
        (*con)->setAutoCommit(false);
        ......大量DML操作
        (*con)->commit();
        if ((++count) % 100 == 0)
            (*con)->close();
    }
}

c++版本内存结构分析(地址值纯属虚构)

数据库未关闭导致的故障分析

说明:

1.mergeHotelTree方法内定义con变量为指针,mergeHotelTreeHandleDB方法形式参数为二级指针,这种做法目 的就是把mergeHotelTree方法栈内con变量地址传递到了mergeHotelTree,当mergeHotelTree做参数拷贝时,是拷 贝的mergeHotelTree的con变量地址,当mergeHotelTreeHandleDB对con进行操作时,实际是对 mergeHotelTree的con操作.

重点是理解:mergeHotelTree栈内变量con存储的是mergeHotelTreeHandleDB栈内变量的地址。