一、问题描述
两任务并发访问同一数据库表中的记录,出现死锁,如下是其中一个任务的代码:
二、解决方案一
确保两个线程访问同一个数据库表的记录的顺序都是一致的。
因为:假设线程(一)目前处理的状况是已更新的为数据集A,剩下未更新的为数据集B;由于两线程访问数据库表的顺序是一致的,故必须是先访问数据集A中的记录,然后再访问数据集B中的记录,因此线程(二)肯定是先访问数据集A中的记录,而此时必须等待,直至线程(一)commit或rollback释放全部的“行锁”。
三、解决方案二
将
ResultSet.CONCUR_UPDATABLE 表示sql语句是要做更新操作,那么在更新这批记录集前,就会先拿到"页锁";如果这批记录集的部分记录的“行锁”未释放,则等待这批记录的“行锁”全部释放,获取“页锁”。在“页锁”释放掉之前,别人不可能拿到这批记录集中的任何一条记录的“行锁”,也就不可能发生死锁。
两任务并发访问同一数据库表中的记录,出现死锁,如下是其中一个任务的代码:
Connection dbConn = DAMContext.getConnection(); String userAccountSql = createAccountSql(exportDate); List accountList = new ArrayList(); try { PreparedStatement pstm = dbConn.prepareStatement(userAccountSql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); ResultSet result = pstm.executeQuery(); while (result.next()) { UserAccount account = new UserAccount(); //omited accountList.add(account); } String updateSql = createExpireSql(exportDate); PreparedStatement updatePs = dbConn.prepareStatement(updateSql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
//ResultSet.CONCUR_READ_ONLY 表示sql语句不会做更新处理, //这就意味者在操作这批记录时,不会首先获取这批记录的"页锁",别人仍旧可以同时对这批数据进行并发的update操作。 ResultSet updateResult = updatePs.executeQuery(); //执行update操作 //在dbConn.prepareStatement()中指定的是不做update处理,而实际上却执行了update操作 //导致更新这批记录时:更新到某条记录时,才获取这条记录的"独占行锁"。 //这就留下了发生死锁的隐患:在执行过程中,一部分已更新的记录集(A)获取了锁;另一部分还没有更新的记录集(B)没有获取锁 //如果此时,有另一线程已经update了记录集(B)中的记录,并且未释放锁;同时又等待记录集(A)中记录的锁来进行update操作,这样死锁就发生了。 } catch (Exception e) { e.printStackTrace(); try { dbConn.rollback(); } catch (SQLException _ex) { _ex.printStackTrace(); } } finally { try { dbConn.setAutoCommit(true); } catch (SQLException _ex) { _ex.printStackTrace(); } finally { DAMContext.freeConnection(dbConn); } }
//ResultSet.CONCUR_READ_ONLY 表示sql语句不会做更新处理, //这就意味者在操作这批记录时,不会首先获取这批记录的"页锁",别人仍旧可以同时对这批数据进行并发的update操作。 ResultSet updateResult = updatePs.executeQuery(); //执行update操作 //在dbConn.prepareStatement()中指定的是不做update处理,而实际上却执行了update操作 //导致更新这批记录时:更新到某条记录时,才获取这条记录的"独占行锁"。 //这就留下了发生死锁的隐患:在执行过程中,一部分已更新的记录集(A)获取了锁;另一部分还没有更新的记录集(B)没有获取锁 //如果此时,有另一线程已经update了记录集(B)中的记录,并且未释放锁;同时又等待记录集(A)中记录的锁来进行update操作,这样死锁就发生了。 } catch (Exception e) { e.printStackTrace(); try { dbConn.rollback(); } catch (SQLException _ex) { _ex.printStackTrace(); } } finally { try { dbConn.setAutoCommit(true); } catch (SQLException _ex) { _ex.printStackTrace(); } finally { DAMContext.freeConnection(dbConn); } }
二、解决方案一
确保两个线程访问同一个数据库表的记录的顺序都是一致的。
因为:假设线程(一)目前处理的状况是已更新的为数据集A,剩下未更新的为数据集B;由于两线程访问数据库表的顺序是一致的,故必须是先访问数据集A中的记录,然后再访问数据集B中的记录,因此线程(二)肯定是先访问数据集A中的记录,而此时必须等待,直至线程(一)commit或rollback释放全部的“行锁”。
三、解决方案二
将
PreparedStatement updatePs = dbConn.prepareStatement(updateSql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
修改为:
PreparedStatement updatePs = dbConn.prepareStatement(updateSql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
ResultSet.CONCUR_UPDATABLE 表示sql语句是要做更新操作,那么在更新这批记录集前,就会先拿到"页锁";如果这批记录集的部分记录的“行锁”未释放,则等待这批记录的“行锁”全部释放,获取“页锁”。在“页锁”释放掉之前,别人不可能拿到这批记录集中的任何一条记录的“行锁”,也就不可能发生死锁。