关于delphi开发和MSsql的数据库锁定的讨论(100分)

  • 主题发起人 主题发起人 3p
  • 开始时间 开始时间
3

3p

Unregistered / Unconfirmed
GUEST, unregistred user!
我初次采用delphi开发mssql6.5的数据库应用系统,对系统开始使用后出现了严重的更新锁定问题,不知有否最佳的更新策略,我的某个更新操作form设计
如下:query+updatesql
query的cacheupdate,requestlive为true
query的sql为一般的select....语句
我用dbgrid接受query的输出结果作为游览,(我的原意是让用户游览的
同时可修改数据)
这是个极普通的设计.但放到多用户的环境下运行时,却发现出现了锁定吊死
的现象,同一时间只能有一个用户可修改数据,另一用户进入界面只能游览,如果
修改/插入,呈现死机现象(在等另一用户退出界面).察看书籍,这应该是数据页
加了锁的问题.在Isql下发出sp_lock2察看程序进入界面时的锁的状况,发现
table已经加上了共享锁SH_PAGE,SH_INT这意味着此时如果另一用户也进入该
界面,他将只能游览,即使把cacheupdate,requestlive设为false还是那样子
把updatesql也删除,单纯query运行测试,还是加了共享锁,看来delphi对sql
语句的返回结果都做了处理加了共享页锁.
我在MSSQL的Isql中编写了一个小事务
begin tran
select * from table1
waitfor delay '00:00:20'
commit tran
运行期间再用sp_locak2察看,并没加上任何的锁,我该如何更好地编写程序既能
方便用户查看,又方便用户的更新操作,避免这恼人的锁的问题?请各方神手发表
看法!谢谢





 
我觉得你不要用UPDATESQL, 而是在DBGRID中自己用另外一个QUERY来更新数据.

即发现用户修改后, 程序用独立的QUERY来UPDATE TABLE. 在更新不成功时(与其它

用户的更新发生冲突)重试几次. 或用事务,先把要修改的表加锁,然后更新,最后结束

事务.
 
我觉得,如果不用delphi自带的updatesql,以及其事务处理功能,那么,每次修改记录
都向数据库服务器发出更新事务,这不符合client database的优化编程规则,利用de
lphi自己的updatesql等事务处理,本来出发点适合client database的优化处理,
但我却很不明白,为什么delphi非要在select * from...的语句返回的数据集加上了
共享锁,这已成了它的默认,用户也(我不肯定)不能在delphi中设置或改变,对select
* from ...,现在我只能在SQL与句中加上"for browse"参数,才可摆脱这恼人的锁
我不知这是否是唯一方法.
按书上说的,编写client database程序,应尽量地把返回的数据集减少,提高程序
的并行性,但我觉得对delphi的dbgrid来说,这似乎掩盖了它的优越点,对于想给用户
提供随意的查询游览,或进一步地查询,限制返回的数据集(不用select *),那令我很
难堪,到底怎样才可获得满意的效果,望各位前辈指点迷津,谢谢!
 
在select 中加共享锁是为了避免脏读,如果你可以允许脏读的话,可以在
select 语句中加Nolock就不加共享锁了。
 
首先,SQL Server本身给每个Select加了页锁,
在Query中执行Select时, Delphi似乎又根据返回数据集的大小来决定是否解开页锁,这应该和返回数据集的Cash有关。
当记录数很多时,用Nolock也是要加琐的。
3p:你的小事务中Select执行时是加锁的,只是时间很快,在Delay时表当然就不加锁了。

 
我已在Mssql中做过测试:
nolock确实另select...返回数据不加锁,但既然是脏读,应该极力回避.
在一个事务中影响select的锁的还有事务的隔离状况设定set transaction
isolation level.....
1. set transaction isolation level read commited(默认)后的事务中,
select 语句执行返回后锁也解除了.
2 set transaction isolation level repeated read后的事务中,
select 语句执行返回后锁保持住,直至事务结束,相当于holdlock.
大家可以测试一下.
但这是mssql的事,我所讨论的是delphi的内部的BDE执行select时,如果不带参数
则返回的数据集大于一页时,都把锁保持住了,这个保持正是症结,很想弄明白它是如何
把sql语句传送给mssql的.
fx所说的执行select时加锁我赞成,但delphi不应select执行后把share锁持住,
因这样其他人就不能修改同一页内的数了.













 
各位高手:
有否更新的提议,请为我彻底解决烦恼。
 
你可以这么做,就可以在 CACHE UPDATE 状态里去掉锁
Try
With Query1 Do
Begin
Open;
FetchAll; //取回所有数据
First;
End;

为什么是这样?下面是我的理解和经验:
首先,可以肯定的是加锁是SQL SERVER干的,DELPHI本身不具有加锁机制
如果你的tQUERY 没有处于 CACHEUPDATE状态的话, 一个查询是这么完成的:
tQuery-->发送SQL 到 BDE--> BDE视 ALIAS里设定的QueryMode是SERVER还是
LOCAL决定是否直接传送SQL到SERVER-->
Sql Server开始查询-->打开第一页数据,给第一页加锁-->查找-->
-->释放第一页的锁,打开第二页,加锁第二页-->.........-->查找结束,释放
共享锁
从上面可以看出, 对于SQL SERVER来说,共享锁只发生在查找的过程中(querying)
而且页锁的加载和解除是由SERVER自动控制决定的。

但是当tQuery在CACHE UPDATE 状态时,情况就不同
( 我用的是DELPHI3。01+bde501)
tQuery打开后,并没有释放掉SHARE 锁, 如果你用了数据集控制,比如DBGRID
tQuery取回一定记录后,停在这个DBGRID的最后一行所处的页里,把锁放在这里
没有释放掉. 所以当你用 FetchAll 把所有记录 "download" 到本地内存中
时,这个页锁就会消失.

btw: 令我困惑的是, 不知 CACHEUPDATE是如何控制server不要释放锁的。
因为SQLSERVER和DELPHI之间采用了同步查询方式
SQL SERVER一次遍历完所有数据后,才会把结果集通知给 bde.也就是说
tQuery并不会在SQL SERVER查找到半路的某一页时得到消息。
除非是tQuery运行于 跟踪模式(与Sql Monitor一样,使用了SQL SERVER里的Cursor)

另外,单个share 锁不会影响其他用户的更新,这一点你可以实验一下就知道
 
上面所说的
"另外,单个share 锁不会影响其他用户的更新,这一点你可以实验一下就知道"
是我在Request live:=False时, CacheUpdate不必Request live:=True
 
用FetchAll的方法的确可以去掉锁,很好用。
不过经实验,锁的状态与Query的CACHE UPDATE是无关的(我用的是Delphi4+BDE5.01),似乎只和写的SQL语句(是否noLock)及返回的数据集的大小有关。

 
谢谢各位,因工作较忙,建议暂时还没实验过,稍后将尝试,但有点不明,fx说得不错
单纯的query构件在只读的情况下,当返回的数据集超过了SQL的页的限量,sql回加
上share锁的,只是我对阿松说的"CacheUpdate不必Request live:=True"疑惑,
如果想query也能修改数据不是必须 CacheUpdate和Request liv都为True吗?
还有的是阿松说的"单个share 锁不会影响其他用户的更新",我不持相同看法,按书
上说的,如果别的用户想修改的记录正位于您锁住的同一页内,则另一用户必须等待
如果您不作任何操作,不转出此页或退出操作,则用户不得不等待,此时看上去象是
死机了,对用户和开发人员来说都很痛苦。如果此时您也做出了修改操作,因另一用
户也对此页发出了共享锁,情况马上进入了真正的死锁了,双方都无法解开,这种叫
转换死锁,用delphi,不用阿松你说的fetch all,很容易会测试出来,阿松你试过吗
?欢迎继续提议,谢谢!
 
To 3p:
我用的是 MS SQL SERVER65 在使用 Query的CACHEUPDATE时,我总是和UPDATESQL
搭配的一起使用,从来没有设置过 Request live, 工作的没错哦.

关于单个的 share 锁,确实有问题, 之所以昨日没有发现问题,
是我昨日更新的非锁定页 :-)

另外 ,如果你使用显式事务控制就没这问题
database1.starttransaction;
query1.Open;
database1.commit;
没有锁,但有时间等待,实际上是进行了FETCH ALL(用SQL MONITOR一看就知)
用SQL MONITOR跟踪的结果你能发现 QUEYR的隐式事务只是 "download" 了
DBGRID里显示的那几条记录,并没有 COMMIT TRAN
所以会有SHARE LOCK, 但是问题就在这里如果你在这里 COMMIT (可以在BDE别名的 MAX ROWS里设置,默认-1 是取得全部记录),实际上是结束了
事务,当用户下拉光标想看下几条记录时就没有数据了!

所以我认为这问题是无解的, QUERY的查询方式如果没有加 NOLOCK
则启动事务时就决定了一定会有SHARE LOCK加载, 而你让QUERY 查询几条
就结束事务显然不对。如果你让QUERY去FETCH ALL的话,小表还可以
万级,十万级的大表就惨了。 这问题以前想过,甚至想去修改TQUERY的源代码
但实际上修改DELPHI的源代码是没有用的, 因为我当时想让QUERY 有下载几条记录
就停止,再次下拉DBGRID滚动条让QUERY再去查。 但是这种思路是不对的
因为从最根本上说 无法让 sql server 支持这种方式
select * from tbl1 (start_qry_point,end_qry_point)
也就是说,SQL SERVER没有断点续查,呵呵
SQLSERVER7里据说是支持,我实验了一下 SQL7 'select top 50 from dbo.tbl1'
很有趣.

所以只好跟3P兄说 : noway 了, 呵呵。没有根除的办法,只好
缩小查询规模,缩小事务尺寸,缩小SQL SERVER 上的PAGE SIZE到最小(减少每页锁定的记录行数), 还有一种反向的方法,是制造大尺寸的记录
这样一条记录的尺寸大了, 那么一页内的记录数相对就少了, 如果一条记录到了
2K的尺寸,那么SQL SERVER的页锁就是行锁了,呵呵。



 
比较简单的方法是:
SQL查询语句:
select * from Table1 NOLOCK where 条件
~~~~~~ 加上此项关键字, 可以使SQL Server不
对Table1的记录进行加锁,
你可以检测加锁的情况.

至于Cached Updates和 Update SQL 的使用我想你应该见过Delphi提供的
例子Db/Cacheup.


 
to SeaSky:
3p前面提到了,不希望使用NOLOCK,
CACHE UPDATE和UPDATE SQL大家都会使,在讨论的问题是如何
避免CACHE UPDATE 的 SHARE LOCK(而且前提是不使用NOLOCK)
 
多人接受答案了。
 
后退
顶部