AutoCommit
the automatic commit indicator (SQL Server only).
pb帮助中简单的这样描述这个属性,自动提交的指示器,只针对 SQL SERVER 数据库。
但有时候我们看到了令人十分费解的的现象。当我们将全局的trsaction 的 autocommit 置为 false 我们在数据窗口的保存脚本中,仅仅简单的写上一句 dw_1.update(),并不进行commit; ,数据窗口是能够进行更新的。
这看起来视乎非常不可思议,这样的话,autocommit 有何意义呢?commit 又有何意义呢?
大家忽略了一个另外的参数commitondisconnect 这只是 数据库的一个连接参数,并不是transaction 的一个属性。他的含义是:如果该值为“yes”,那么不管autocommit 是true还是false,在disconnect之前,如果还没有没有提交的DML,transaction将会自动的提交一次。
这样我们上面碰到的问题就不能解释了。注意这个参数的默认值是YES,也就是说如果你不主动的这样设置的话 SQLCA.DBParm = "CommitOnDisconnect='No'" ,你所做的修改将会保存进数据库。这个看似实用的功能却经常会给我们带来麻烦,导致数据的不一致性。
例如 当一个较长的事务执行了前面几步尚未Commit时发生数据库连接断开(非法操作、强行退出、死机等)此时会发生已执行的SQL不能回滚,但在退出前transaction 会自做多情地 将已经执行但还没来得及提交的DML 提交,从而导致数据错误(如库存已加但入库单却未打上入库标志) 为防止此类错误发生应设置为:数据库连接断开前不自动提交,即CommitOnDisconnect='No'
再回到最开始提出的那个问题,如果我们将CommitOnDisconnect置为 yes,而 autoCommit 置为 false的话,将会发生更加严重的后果,就是 如果我们在 程序中 有 DML 或者 数据窗口的update()时如果,我们忘了手动的Commit的话,被操做的那个或那些表将会被锁住,其余的人不能访问,或者已经将表的数据读到本地,但不能保存,直到第一个人访问该表的那个Transaction 断开连接。
上面说的是忘了写commit的情况,实际上即使你写了,commit ,如果commit和DML或者update()如果相隔太远的话,也会在执行DML或者update() 和 执行 commit 之间的时间内被锁住
可以试着做这样一个实例来验证:
假设有表t_test ,仅一个字段 test numeric 10
做一个数据窗口对象 d_test,其SQL语句为 select test from t_test
做一个窗口 w_test,有如下控件 dw_1 ,cb_new,cb_save,cb_commit,cb_disconnect,cb_exit
窗口open事件为 dw_1.dataobject = 'd_test'
dw_1.settransobject(sqlca)
dw_1.retrieve()
按钮的click 事件分别为 cb_new :dw_1.scrolltorow(dw_1.insertrow(0))
cb_save:dw_1.update()
cb_commit:commit;
cb_disconnect:disconnect;
cb_exit:close(parent)
当然 Application 的open事件里要将autocommit 设为 FALSE 将commitondisconnect 设为 yes
典型代码为为
SQLCA.DBMS = "MSS Microsoft SQL Server 6.x"
SQLCA.Database = "qhet"
SQLCA.ServerName = "."
SQLCA.LogId = "sa"
SQLCA.AutoCommit = false
SQLCA.DBParm = "CommitOnDisconnect='yes'"
connect using sqlca;
open(w_test)
运行后点new 按钮,填上符合的数据后,点 save ,然后,在PB的数据库画板中执行任何关于表t_test的sql语句均无法执行,如select * from t_test,将会一直处于等待中,直到你点按钮 commit 或者 disconnect 中的任何一个,sql语句才能得以执行。
上面这个小例子模拟了以上所说的过程。
以前我曾遇到过遇到过这些问题,当时不知道什么原因,现在总算恍然大悟了。
那么我们该怎么解决这个问题了.
可以这样,我们将autocommit 设为true,将commitondisconnect 设为 no,
autocommit 设为true 有如下好处,1.如果我们的更新很简单,比如仅仅涉及对一个表,那么我们可以仅仅写一条 upate()就行了,将commit 或rollback的工作交给tansaction 来完成,考虑oracle和其他数据库,最好不要这样做,但如果确定是针对 SQLserver的,这样做师妹有问题的。
2.杜绝了上面说的长时间等待的情况,即使我们忘了写commit,程序会自动commit,其他用户不用等待。
但这样做又有一个隐患,即如果要对多个表进行更新,我么不能简单的将commit和rollback 托付给transaction,如果我们这样做将会使数据不完整,也就是说transaction将会自动 一个表一个表的commit或者rollback,这样就会出现如 订单数据保存进数据库,但订单明细没有保存进数据库 的问题
我们可以这样来处理一下
做以下三个全局函数
//************************************
gf_begin_transaction
//参数ao_environment 类型 transaction
//返回值类型 boolean
ao_Environment.AutoCommit = False
if ao_Environment.SQLCode<>0 then return False
return True
//***********************************************
//**********************************************
gf_commit_transaction//参数返回值同前一个
Commit Using ao_Environment;
if ao_Environment.SQLCode<>0 then return False
ao_Environment.AutoCommit = True
if ao_Environment.SQLCode<>0 then return False
return True
//************************************************
//************************************************
gf_rollback_transaction//参数返回值同前一个
Rollback Using ao_Environment;
if ao_Environment.SQLCode<>0 then return False
ao_Environment.AutoCommit = True
if ao_Environment.SQLCode<>0 then return False
return True
//********************************************
这样我们一旦又多个表更新的情况可以使用l类似这样的结构来处理
gf_begin_transaction(SQLCA)
dw_1.update()
dw_2.update()
delete from table_1......
insert into table_2 ......
...................
...............
if sqlca.sqlcode = 0 then
gf_commit_transaction(sqlca)
retrun
else
gf_rollback_transaction(sqlca)
//error handle code here
end if
考虑到autocommit对除了sqlserver以外的其他数据库不起作用,我们可以在每一个需要更新的地方都已这三个函数来包围。
这样就可以万无一失了。
说明:1.autocommit 仅对sqlserver 起作用,而commitondisconnect 对大多数数据库都起作用,可以查看相应DBMS的手册
2.autocommit 仅在 PB 脚本中起作用,而在数据库画板中是不起作用的,不如即使你在database profile 中将 autocommit 设为false,在数据库画板中执行的DML语句也会马上被commit掉