连接RAC时出现的一个死锁异常

平台的路由应用需要读取一个表中的记录,优先读取SMS_ID比较小的记录;并且因为会有两个路由应用同时工作,为避免重复读取,需要在读取时锁定记录,读完后将这些记录删除;这就要用到Oracle中的select for update语句;排序后的记录无法使用for update子句;只好先用普通查询,用order by方式查出前N条记录的SMS_ID,得到结果中最大的SMS_ID,然后用  WHERE SMS_ID <= 最大值  for update 的方式锁定并获取相应的记录。

代码如下:  

Query query = session.createQuery("select s.smsId from " + entityName
						+ " s order by s.smsId");
List<BigDecimal> idlist = query.setMaxResults(rowNum).list();

//如果结果为空,说明表中没有记录,应直接返回。
if (idlist.size() == 0) {
	transaction.rollback();
	return null;
}

query = session.createQuery("from " + entityName
		+ " s where s.smsId <= ?");
query.setBigDecimal(0, idlist.get(idlist.size() - 1));
query.setLockMode("s", LockMode.UPGRADE);
dataList  = query.list();

 

在本机测试,两个应用同时开启,都工作正常,没有出现重复读取的现象,也没有出现任何异常。

部署到产品线后,两个路由应用却时不时抛出deadlock异常;流量越大的时候,出现deadlock的可能性越高,在运行了几天后的一个下午,两个路由应用都死掉了,导致数据大量积压。只开一个路由应用的话,不会出现deadlock异常。

奇怪是在本机测试时,不管压上多少的流量,都不会出现这个异常;找数据库的DBA拿日志,由于日志被删掉,不会再生成,就拿不到死锁的日志,DBA提到可能是问题源于本机测试连的是单数据库,而产品线上连得是采用RAC的两个数据库,两个数据库操作相同的数据;在产品线上可能一个路由通过某个数据库锁定记录,而另一个应用通过另一个数据库锁定记录,比连单数据库的情况要复杂些;可能是这种复杂性导致本机没有问题的操作在产品线上却抛出deadlock异常。

没有拿到日志,搞不清楚是怎么死锁的,只好进行尝试;可以采取的方案的有

1. 同事提出的过滤条件不用 SMS_ID <= 最大值,而是用 SMS_ID in (),这样也许能更精确控制锁定的记录;修改后的代码如下 

query = session.createQuery("from " + entityName
						+ " s where s.smsId in (:ids) ");
query.setParameterList("ids", idlist);
query.setLockMode("s", LockMode.UPGRADE);
dataList  = query.list()

 

2. 采用原生SQL,在for update后面加上skip locked,这样会话在锁定记录时,即使有些记录已经被其他会话锁定,也不会阻塞,直接跳过被锁定的记录,返回满足过滤条件的未锁定记录;不会阻塞,应该就不会发生死锁;修改后的代码如下

String tableName = HibernateSessionFactory.getTableName(entityName);
SQLQuery sqlQuery = session.createSQLQuery("select * from " + tableName 
		+ " where SMS_ID <= ? for update skip locked");
sqlQuery.setBigDecimal(0, idlist.get(idlist.size() - 1));
sqlQuery.addEntity(entityName);
dataList  = sqlQuery.list();

 

 

先采用第一个方案,到产品线上测试,发现两个应用就不再抛出deadlock异常;既然问题解决了,就没有到产品线上测试第二个方案,毕竟产品线不是测试机。

    对RAC的了解不多,想不明白为什么这么一改就好了,先记着吧。

猜你喜欢

转载自epy.iteye.com/blog/1632042