我从多线程环境调用DAO类,它触发select查询从表中获取一行(获取ticketId),然后更新同一行(使用customerId)。这发生在同一个交易中。我的数据库是SQL Server。当我触发select查询时,我尝试将行级别锁定(WITH ROWLOCK),以便其他线程不会获得相同的行。我的DAO类如下(这里只显示重要的代码段):
public void saveCustomerTicketUsingJDBC(String customerId) {
Session session = getSession();
//Session session = SessionFactoryUtils.getSession(getSessionFactory(), true);// Have tried this too
try {
session.getTransaction().begin();
Query query1 = session.createSQLQuery("select TOP 1 * from CustomerTicket WITH (ROWLOCK) where customerId is null");
Object[] customerTicket = (Object[])query1.uniqueResult();
Integer id = (Integer)customerTicket[0];
ticketId = (String)customerTicket[1];
logger.debug("Got ticket id -->"+ticketId);
Query query2 = session.createSQLQuery("update CustomerTicket " +
"set customerId = :customerId " +
"where ticketId = :ticketId");
query2.setParameter("customerId", customerId);
query2.setParameter("ticketId", ticketId);
logger.debug("QUery 2 executeUpdate : customerId : "+customerId+", ticketId :"+ticketId);
int result = query2.executeUpdate();
logger.debug("result >"+result +", customerTicketId ----------->"+customerId+", ticketId ------------>"+ticketId);
//session.flush();
//session.clear();
session.getTransaction().commit();
} catch (Exception e) {
logger.error("Exception while saving customer ticket-->"+e,e);
} finally {
if (session != null) {
session.close();
}
}
}
我产生了4个线程。我在日志文件中看到的是,所有四个线程都到达数据库表中的相同记录。
2014-02-26 22:41:29.183 DEBUG [pool-3-thread-2] CustomerTicketDAO.java:83 Got ticket id -->4
2014-02-26 22:41:29.183 DEBUG [pool-3-thread-4] CustomerTicketDAO.java:83 Got ticket id -->4
2014-02-26 22:41:29.184 DEBUG [pool-3-thread-3] CustomerTicketDAO.java:83 Got ticket id -->4
2014-02-26 22:41:29.184 DEBUG [pool-3-thread-1] CustomerTicketDAO.java:83 Got ticket id -->4
首先,这不应该发生,对吗?我期待看到每个线程应该得到不同的行。
然后我看到只有一个线程能够成功更新数据库。
2014-02-26 22:41:29.408 DEBUG [pool-3-thread-1] CustomerTicketDAO.java:93 result >1, customerTicketId ----------->CustomerId_0, ticketId ------------>4
另外三个线程在线上死掉:
int result = query2.executeUpdate();
我不明白其他三个线程会发生什么,因为我在日志文件中没有看到任何内容。
有人请帮助我。
由于 拉吉
答案 0 :(得分:3)
目前还不清楚(至少在我看来),在3个失败的tx的情况下,究竟是什么错误Sql Server和hibernate返回。但是他们失败并不奇怪。
(行)锁不是队列,也不是过滤器。四个选择查询将返回同一行并不奇怪,因为锁的存在不会更改数据,因此也不会更改查询结果 - 它只是保护访问数据。我怀疑,但不知道,这可能是通过hibernate缓存查询来绕过锁。
根本问题是您有四个进程都争用单个资源(第一个未分配的票证)。虽然正确实施的行锁定方案可行,但它的选择较差,因为它的扩展性不如替代方案。
使用同步块编写dao会更好。这将处理appserver中的多个线程同时竞争资源的情况。最简单的方法是:
public synchronized void saveCustomerTicketUsingJDBC(String customerId) {
Session session = getSession();
...
}
这很好地处理了单个appserver的情况,尽管你需要知道不能保证线程的执行顺序。我怀疑,根据您的变量名称,这对您来说可能是一个问题,但即使如此,此解决方案也不会使问题更严重。
如果您有多个应用程序服务器,那么您仍然可以有多个进程争用同一资源。同样,这可以用(悲观的)行锁解决,但我怀疑你的乐观锁定解决方案会更好。乐观锁看起来像这样:
while (true) {
session.getTransaction().begin();
try {
Query query1 = session.createSQLQuery("select TOP 1 * from CustomerTicket where customerId is null");
Object[] customerTicket = (Object[])query1.uniqueResult();
Integer id = (Integer)customerTicket[0];
ticketId = (String)customerTicket[1];
logger.debug("Got ticket id -->"+ticketId);
Query query2 = session.createSQLQuery("update CustomerTicket " +
"set customerId = :customerId " +
"where ticketId = :ticketId AND customerId is NULL");
// Notice the AND clause!!!
query2.setParameter("customerId", customerId);
query2.setParameter("ticketId", ticketId);
logger.debug("QUery 2 executeUpdate : customerId : "+customerId+", ticketId :"+ticketId);
int updateCount = query2.executeUpdate();
logger.debug("updateCount >"+updateCount +", customerTicketId ----------->"+customerId+", ticketId ------------>"+ticketId);
// Did someone beat us to it?
if (updateCount == 0) {
session.getTransaction().rollback();
continue;
}
// Nope - we're winning so far, but the race isn't over yet...
session.getTransaction().commit();
} catch (OptimisticLockException ex) {
logger.debug("Darn, someone DID beat us to it");
session.getTransaction().rollback();
continue;
} catch (Exception ex) {
...
}
break;
}