存储过程未提交"/>
java存储过程未提交
通过JAVA调用SQLSERVER存储过程时,存储过程内部发生了类型转换异常,在类型转换异常发生之前已经将事务打开了,并且在类型转换那一行没有采用像如下错误处理:
if @@error<>0 then
goto err
end
导致了过程在发生异常的时候直接中断返回JAVA,此时过程内部的事务并没有提交,根据MICROSOFT的文档如果存储过程内部有事务并且事务没有提交,那么JAVA的事务也不会提交,JAVA的事务须在过程事务提交以后才会被提交。当前系统也采用了连接池,此时JAVA容器直接将没有提交事务的连接再次放回连接池中,这样就影响到了后续的交易,如果后续交易使用到了该连接就会报“无法提交事务”的异常,以下是我在博客园(/)看到的一篇文章,对这样的错误讲得非常清楚:
有个客户跟我说,他们把weblogic从923升级到923以后,总是提示failed to resume transaction的。当时没有看到任何信息,以为跟JTA超时有关,让客户把JTA timeout加大,同时把下面的设置加入jdbc-config.xml中。客户反馈过来说:还是不行。 :(
true
true
比较郁闷,后来想想,客户的jdbc是使用non-XA的driver, 同时将emulate-2pc设为了true,上面连个参数是针对XA的,测试没有效果应该是正常的。:) 客户把具体错误信息给我发了过来,如下:
####<2008-10-27 上午10时35分28秒 CST> <> <> <> <1225074928234> 奇怪了,对于non-XA的connection, weblogic做测试的时候,应该是不会其transaction的,而且是直接拿底层的物理connection来做个select,怎么会有transaction resume呢? 是不是底层物理连接出现了问题? 跟客户沟通了一下,大概了解了他们的应用:通过jdbc调用SQL Server的stored procedure,而stored procedure中会起自己的transaction。 Tx中的操作分两种类型:
1:业务简单,数据量大
2:数据不大,但业务比较复杂
总之,TX中的SQL执行时间比较长。
开始怀疑跟客户的stored procedure有关,建议他先去掉里面的TX,果然奏效。因为transaction一直是我比较模糊的董东,也不敢跟客户说:你不能这么写,这么写是不可以的。毕竟自己底气不足啊。不是很清楚jdbc connection的auto commit提交的到底是哪个事务,是driver的tx,还是stored procedure里的tx。应该是前者吧。花了一上午,自己搭了个测试环境,终于复现了这个问题:
存储过程:
1
create proc dbo.TestProc
2
as
3
begin transaction
4
waitfor delay
'
00:02:00
'
5
insert into dbo.TestT_1 values(
'
test
'
)
6
commit
JDBC 代码:
1
package
com.bea.cs.test.jdbc;
2
3
import
com.bea.cs.test.utils.JNDIRetriver;
4
import
java.sql.
*
;
5
import
java.io.
*
;
6
import
javax.transaction.
*
;
7
8
public
class
SQLServerJDBCTest {
9
10
public
static
void
main(String args[])
11
{
12
SQLServerJDBCTest test
=
new
SQLServerJDBCTest();
13
14
for
(
int
loop
=
0
; loop
<
15
; loop
++
)
15
test.callProc(
"
jdbc/SQLServerNonXADS
"
, loop);
16
17
try
{
18
Thread.currentThread().sleep(
10000
);
19
}
catch
(Exception e){}
20
21
for
(
int
loop
=
0
; loop
<
15
; loop
++
)
22
test.checkAutoCommit(
"
jdbc/SQLServerNonXADS
"
);
23
}
24
25
public
void
checkAutoCommit(String dsName)
26
{
27
CheckAutoCommitThread cacThread
=
new
CheckAutoCommitThread(dsName);
28
cacThread.start();
29
}
30
31
class
CheckAutoCommitThread
extends
Thread
32
{
33
private
String dsName
=
null
;
34
35
public
CheckAutoCommitThread(String ds){
36
dsName
=
ds;
37
}
38
39
private
void
callProc(String dsName,
int
loop)
40
{
41
ProcThread procThread
=
new
ProcThread(dsName, loop);
42
procThread.start();
43
}
44
45
class
ProcThread
extends
Thread
46
{
47
private
String ds
=
null
;
48
private
int
id
=
-
1
;
49
50
public
ProcThread(String dsName,
int
loop)
51
{
52
ds
=
dsName;
53
id
=
loop;
54
}
55
56
public
void
run()
57
{
58
String url
=
"
t3://10.130.2.90:7001
"
;
59
String sql
=
"
{ call TestProc() }
"
;
60
Connection conn
=
null
;
61
JNDIRetriver retriever
=
new
JNDIRetriver(url);
62
try
{
63
conn
=
retriever.getJBDCConnection(ds);
64
boolean
autoCommit
=
conn.getAutoCommit();
65
CallableStatement cstmt
=
conn.prepareCall(sql);
66
67
//
start a thread to close current connection, so that a connection
68
//
that attachs a tx will be returned to connection pool and when
69
//
it's retrieved from connection pool by other client, the error
70
//
will be reproduced.
71
ConnCloseThread closeThread
=
new
ConnCloseThread(conn, id);
72
closeThread.start();
73
long
start
=
System.currentTimeMillis();
74
System.out.println(
"
execute-
"
+
id
+
"
starts at:
"
+
start
/
1000.0
);
75
cstmt.execute();
76
long
end
=
System.currentTimeMillis();
77
System.out.println(
"
statement
"
+
id
+
"
execute:
"
+
(end
-
start)
/
1000.0
);
78
conn.close();
79
}
catch
(Exception e)
80
{
81
try
{
82
System.out.println(
"
connection is closed for exception:
"
+
e.getMessage());
83
conn.close();
84
}
catch
(Exception e1){}
85
e.printStackTrace();
86
}
87
}
88
}
89
90
class
ConnCloseThread
extends
Thread
91
{
92
private
Connection connection
=
null
;
93
private
int
id
=
-
1
;
94
95
public
ConnCloseThread(Connection conn,
int
loop){
96
connection
=
conn;
97
id
=
loop;
98
}
99
100
public
void
run()
101
{
102
try
{
103
Thread.currentThread().sleep(
10000
);
104
//
connection.rollback();
105
long
start
=
System.currentTimeMillis();
106
System.out.println(
"
closeConn-
"
+
id
+
"
starts at:
"
+
start
/
1000.0
);
107
connection.close();
108
long
end
=
System.currentTimeMillis();
109
System.out.println(
"
close connection
"
+
id
+
"
takes:
"
+
(end
-
start)
/
1000.0
);
110
}
catch
(Exception e){}
111
}
112
}
113
}
114
115
测试结果和预想的有点出入:ConnCloseThread中关闭连接的时候,不是立刻返回的。Connection.close()会触发Connectionmit(),而因为调用的存储过程中,存储过程起了自己的事务,connectionmit()必须等到存储过程结束才能完成(这个是microsoft论坛上看到的)。如果所有connection.close()都等到tx commit或rollback完成才执行的话,这个问题就不会出现了。看看我的测试结果:
statement 5 execute:表示从存储过程调用开始,到调用返回的时间
close connection 5 takes:表示关闭连接耗费的时间(也就是connectionmit()等待存储过程事务结果的时间)
statement 5 execute: 125.922
close connection 5 takes: 148.39
statement 14 execute: 130.031
close connection 14 takes: 148.39
statement 2 execute: 134.031
close connection 2 takes: 148.39
statement 6 execute: 138.14
close connection 6 takes: 148.406
statement 8 execute: 142.14
close connection 8 takes: 148.406
statement 0 execute: 146.156
close connection 0 takes: 148.406
statement 3 execute: 162.39
close connection 3 takes: 168.625
statement 11 execute: 166.39
close connection 11 takes: 168.625
statement 13 execute: 120.0
close connection 13 takes: 115.359
statement 12 execute: 150.265
close connection 12 takes: 148.406
statement 9 execute: 154.281
close connection 9 takes: 148.406
statement 1 execute: 158.39
close connection 1 takes: 148.406
statement 4 execute: 170.5
close connection 4 takes: 168.625
statement 10 execute: 174.515
close connection 10 takes: 168.625
statement 7 execute: 178.609
close connection 7 takes: 168.625
####<> <> <> <1225187966102> ####<> <> <> <1225187966132> ####<> <> <> <1225187966142> ####<> <> <> <1225187966162> ####<> <> <> <1225187966172> ####<> <> <> <1225187966172> ####<> <> <> <1225187966182>
从测试结果来看,凡是close connection耗时比execute statement短的,连接(物理连接)都会报出该问题。分析原因:通过weblogic datasource获取的connection并不是物理connection,而是由weblogic wrapped的connection。这些conection在被close后,并不会关闭物理连接,而只是将物理连接还池。我们对connection的所有操作,最终都会被delegated到底层物理连接上,即commit(),rollback()最终都是在物理连接上执行。如果上面的connection.close(),底层物理连接没有等到存储过程事务结束就返回的话,那么物理连接上应该还带有此次操作的事务,而weblogic这边不会关系物理连接的情况,直接将连接放入connection pool供其它客户端使用。这时候如果设定了test on reserve的话,下次客户端从data source获取连接时,weblogic会检查这个物理连接,作一个select操作的,这个有问题的连接就会暴露出来,也就是上面的异常。这个问题如果使用driver manager来获取连接的话(如果每次都关闭的话),则不会出现,因为使用的物理连接每次都是不同的。还好,weblogic会帮忙重新创建有问题的连接。原因大概了解了,但这是谁的问题呢? 为什么connection.close()不等存储过程的事务结束?
结论:一般而言,我们不建议通过JDBC调用存储过程的时候,在存储过程中定义事务,应该将tx的管理工作交给jdbc去做。 non-xa如此,xa亦如此,毕竟事务嵌套了以后,管理起来是个问题,完整性更是个问题。
本文出自:冯立彬的博客
更多推荐
java存储过程未提交
发布评论