浏览 197 次
|
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
|---|---|
| 作者 | 正文 |
|
时间:2008-05-15
情况大概是这样的
表table_A,和表table_B,在分属不同的业务逻辑中会以不同顺序进行更新 比如 业务逻辑business_X: getConnection; update table_A; update table_B; commit or rollback; 业务逻辑business_Y: getConnection; update table_B; update table_A; commit or rollback; 测试代码我用几个线程并发进行顺序的控制,关键代码如下
public class TestRun implements Callable<Object>{
private String sql;
private Connection conn;
private long timeOut;
public TestRun(String sql,Connection conn,long timeOut){
this.sql=sql;
this.timeOut=timeOut;
this.conn=conn;
}
public Object call() throws Exception {
Thread.currentThread().sleep(timeOut);
//打印信息,可以无视
System.out.println("start conn="+conn+" , sql='"+sql+"' , time="+((System.nanoTime()%100000000000L)));
System.out.println("");
try{
conn.createStatement().executeUpdate(sql);
}catch(Exception e){
e.printStackTrace();
}
//打印信息,可以无视
System.out.println("end conn="+conn+" , sql='"+sql+"' , time="+((System.nanoTime()%100000000000L)));
System.out.println("");
return null;
}
}
业务逻辑的模拟代码关键部分如下
Connection conn1=ConnectionPool.getConnection();
Connection conn2=ConnectionPool.getConnection();
ExecutorService es=Executors.newFixedThreadPool(10);
TestRun tr1=new TestRun("update table_A set field0='11111111' where id=1;",conn1,1000);
TestRun tr2=new TestRun("update table_B set field0='22222222' where id=1;",conn2,3000);
TestRun tr11=new TestRun("update table_B set field0='11111111' where id=1;",conn1,5000);
TestRun tr22=new TestRun("update table_A set field0='22222222' where id=1;",conn2,7000);
es.submit(tr1);
es.submit(tr11);
es.submit(tr2);
es.submit(tr22);
最后打印的结果和异常如下 start conn=com.mysql.jdbc.ConnectionImpl@17ee8b8 , sql='update table_A set field0='11111111' where id=1;' , time=23969656199 end conn=com.mysql.jdbc.ConnectionImpl@17ee8b8 , sql='update table_A set field0='11111111' where id=1;' , time=23971563424 start conn=com.mysql.jdbc.ConnectionImpl@1995d80 , sql='update table_B set field0='22222222' where id=1;' , time=25970551285 end conn=com.mysql.jdbc.ConnectionImpl@1995d80 , sql='update table_B set field0='22222222' where id=1;' , time=25971987501 start conn=com.mysql.jdbc.ConnectionImpl@17ee8b8 , sql='update table_B set field0='11111111' where id=1;' , time=27970671691 start conn=com.mysql.jdbc.ConnectionImpl@1995d80 , sql='update table_A set field0='22222222' where id=1;' , time=29970794053 end conn=com.mysql.jdbc.ConnectionImpl@17ee8b8 , sql='update table_B set field0='11111111' where id=1;' , time=29973001596 com.mysql.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1042) at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:957) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3376) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3308) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1837) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1961) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2537) at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1564) end conn=com.mysql.jdbc.ConnectionImpl@1995d80 , sql='update table_A set field0='22222222' where id=1;' , time=30003110730 at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1485) at TestRun.call(TestRun.java:23) at TestRun.call(TestRun.java:1) at java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source) at java.util.concurrent.FutureTask.run(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.lang.Thread.run(Unknown Source) 如果同样加上两个线程执行 commit() 代码后的结果是 conn1 的所有 sql 执行OK,结论应该是强制性的释放了 mysql 为 conn2 加的锁。 我的意思不是说Hibernate之类的解决方案,Hibernate只是JDBC的封装,说到底这个问题她也是需要面对和解决的。另外,业务逻辑上大锁一加,单线程处理也不用考虑了,效率低不说,把这些数据库级的死锁问题上升到业务层解决也决不是好办法。 希望熟悉JDBC数据库开发的同仁指点一下。我想,这类情况应该很常见才对,不知道通常应该怎么解决? 声明:JavaEye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
|
|
| 返回顶楼 | |
|
时间:2008-05-15
死锁确实是数据库锁定
但是你这样就说他是数据库级别................就过分了吧........ 死锁完全是因为业务问题 再好的数据库 发两条SQL过去 该死锁还是死锁 |
|
| 返回顶楼 | |
|
时间:2008-05-15
ddandyy 写道 死锁确实是数据库锁定
但是你这样就说他是数据库级别................就过分了吧........ 死锁完全是因为业务问题 再好的数据库 发两条SQL过去 该死锁还是死锁 多谢多谢 呃……我说错了,应该不说是数据库级别,或者说,应该是dao层或者持久层关注的东西。 这种问题应该很常见,于是觉得JDBC应该提供了解决方案,比如,简单来说,我想当然的认为既然JDBC检测到了死锁问题,那么哪些资源冲突Connection应该清除,那么实际上Connection可以存储一次事务前几次的操作,然后“rollback”直到释放这些资源,再然后比如 wait(1000);redo()…… 算了算了,自己去看看JDBC的API好了,要是实在没办法只能要么在业务层注意一下(这个貌似治标不治本),要么只好在DAO层给资源加锁(单线程效率……)或者……干脆无视,本次操作快速失败…… |
|
| 返回顶楼 | |
|
时间:2008-05-15
如果真的那么简单......
就不会出现什么死锁了........ 这个只能业务上注意 程序里注意...... P.S:运行一个DAO UPDATE的时候 居然会发给另一个DAO ROLLBACK命令 你不觉得诡异么 |
|
| 返回顶楼 | |
|
时间:2008-05-15
ddandyy 写道 运行一个DAO UPDATE的时候 居然会发给另一个DAO ROLLBACK命令 你不觉得诡异么
貌似我想了想解决的办法就是如此……简单的改了一下模拟的测试用的业务逻辑,第一时间执行 sql1 ,隔4秒执行 sql2
public Object call() throws Exception {
while(true){
Thread.currentThread().sleep(timeOut); try{
conn.createStatement().executeUpdate(sql1);
}catch(SQLException e){
if(e.getErrorCode()==1213){
try{
conn.rollback();
continue;
}catch(Exception ex){
ex.printStackTrace();
}
} }
Thread.currentThread().sleep(4000);
try{
conn.createStatement().executeUpdate(sql2);
}catch(SQLException e){
if(e.getErrorCode()==1213){
try{
conn.rollback();
continue;
}catch(Exception ex){
ex.printStackTrace();
}
}
}
conn.commit();
conn.close();
return null;
}
}
然后main函数里面调用如下: String sql1="update table_A set field0='11111111' where id=1;"; String sql11="update table_B set field0='11111111' where id=1;"; String sql2="update table_B set field0='22222222' where id=1;"; String sql122="update table_A set field0='22222222' where id=1;"; TestRun tr1=new TestRun(sql1,sql11,conn1,1000); TestRun tr2=new TestRun(sql2,sql122,conn2,3000); es.submit(tr1); es.submit(tr2); 这样的顺序就是 第一秒 : conn1.exe(sql1) 第三秒 : conn2.exe(sql2) 第五秒 : conn1.exe(sql11) 第七秒 : conn2.exe(sql22) 当执行到第五秒 conn1.exe(sql11) 会等待 conn2 释放 table_B 资源,执行到 conn2.exe(sql22) 发现死锁,自动强制杀死 conn2 的所有操作,并抛出 SQLException ,打印和查资料看了看,死锁错误代码是1213,于是当检测到错误代码为1213时回滚conn2,然后再从头执行 conn2 的所有操作。实际代码会加上超时或者重试次数等来打断无限循环 测试是通过了,不过似乎也没从根本上解决问题带来: 1.时间片轮转,CPU时间的不可控:比如回滚conn2,立刻又执行conn2.exe(sql2),又死锁,没个准 2.e.getErrorCode()==1213这句代码实在恶心……鬼才知道其他数据库的死锁代码 3.效率问题,整个的回滚……代价太大,不过有选择的回滚,一步一savepoint,很傻很龌龊 ddandyy 写道 这个只能业务上注意 程序里注意......
这句话不是很赞成,总觉得会带来更多复杂性和不可控制性,比如第二个程序员进行系统升级,不通读第一个程序员的代码并排出所有这些会发生冲突的地方,恐怕这种问题的几率蛮大,而这些工作恐怕头痛得很 ddandyy 写道 如果真的那么简单......
就不会出现什么死锁了........ 这句话超级赞成,之前没考虑这些问题,觉得似乎简单,现在回想起来,以前做的项目,碰上这种问题的几率应该有点大,居然都没注意到 |
|
| 返回顶楼 | |
|
时间:2008-05-15
说老实话我觉得这么写很恶心...
如果每个操作都加这个判断就太傻了.... 而如果做到共通的里面....这种自动化的东西 没准会出什么问题....... 我们是在业务上控制避免死锁的...... 操作数据的时候先锁表...for update nowait 锁定之后再修改 而不是修改到一半再退回来 |
|
| 返回顶楼 | |




