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

时间:2022-03-27 02:00:47

简要介绍

在看代码前要必要了解一下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栈内变量的地址。