pb中如何控制并发和控制死锁 转.docx
《pb中如何控制并发和控制死锁 转.docx》由会员分享,可在线阅读,更多相关《pb中如何控制并发和控制死锁 转.docx(12页珍藏版)》请在冰豆网上搜索。
pb中如何控制并发和控制死锁转
pb中如何控制并发和控制死锁转
锁的概述
一.为什么要引入锁
多个用户同时对数据库的并发操作时会带来以下数据不一致的问题:
丢失更新
A,B两个用户读同一数据并进行修改,其中一个用户的修改结果破坏了另一个修改的结果,比如订票系统
脏读
A用户修改了数据,随后B用户又读出该数据,但A用户因为某些原因取消了对数据的修改,数据恢复原值,此时B得到的数据就与数据库内的数据产生了不一致
不可重复读
A用户读取数据,随后B用户读出该数据并修改,此时A用户再读取数据时发现前后两次的值不一致
并发控制的主要方法是封锁,锁就是在一段时间内禁止用户做某些操作以避免产生数据不一致
二锁的分类
锁的类别有两种分法:
1.从数据库系统的角度来看:
分为独占锁(即排它锁),共享锁和更新锁
MS-SQLServer使用以下资源锁模式。
锁模式描述
共享(S)用于不更改或不更新数据的操作(只读操作),如SELECT语句。
更新(U)用于可更新的资源中。
防止当多个会话在读取、锁定以及随后可能进行的资源更新时发生常见形式的死锁。
排它(X)用于数据修改操作,例如INSERT、UPDATE或DELETE。
确保不会同时同一资源进行多重更新。
意向锁用于建立锁的层次结构。
意向锁的类型为:
意向共享(IS)、意向排它(IX)以及与意向排它共享(SIX)。
架构锁在执行依赖于表架构的操作时使用。
架构锁的类型为:
架构修改(Sch-M)和架构稳定性(Sch-S)。
大容量更新(BU)向表中大容量复制数据并指定了TABLOCK提示时使用。
共享锁
共享(S)锁允许并发事务读取(SELECT)一个资源。
资源上存在共享(S)锁时,任何其它事务都不能修改数据。
一旦已经读取数据,便立即释放资源上的共享(S)锁,除非将事务隔离级别设置为可重复读或更高级别,或者在事务生存周期内用锁定提示保留共享(S)锁。
更新锁
更新(U)锁可以防止通常形式的死锁。
一般更新模式由一个事务组成,此事务读取记录,获取资源(页或行)的共享(S)锁,然后修改行,此操作要求锁转换为排它(X)锁。
如果两个事务获得了资源上的共享模式锁,然后试图同时更新数据,则一个事务尝试将锁转换为排它(X)锁。
共享模式到排它锁的转换必须等待一段时间,因为一个事务的排它锁与其它事务的共享模式锁不兼容;发生锁等待。
第二个事务试图获取排它(X)锁以进行更新。
由于两个事务都要转换为排它(X)锁,并且每个事务都等待另一个事务释放共享模式锁,因此发生死锁。
若要避免这种潜在的死锁问题,请使用更新(U)锁。
一次只有一个事务可以获得资源的更新(U)锁。
如果事务修改资源,则更新(U)锁转换为排它(X)锁。
否则,锁转换为共享锁。
排它锁
排它(X)锁可以防止并发事务对资源进行访问。
其它事务不能读取或修改排它(X)锁锁定的数据。
意向锁
意向锁表示SQLServer需要在层次结构中的某些底层资源上获取共享(S)锁或排它(X)锁。
例如,放置在表级的共享意向锁表示事务打算在表中的页或行上放置共享(S)锁。
在表级设置意向锁可防止另一个事务随后在包含那一页的表上获取排它(X)锁。
意向锁可以提高性能,因为SQLServer仅在表级检查意向锁来确定事务是否可以安全地获取该表上的锁。
而无须检查表中的每行或每页上的锁以确定事务是否可以锁定整个表。
意向锁包括意向共享(IS)、意向排它(IX)以及与意向排它共享(SIX)。
锁模式描述
意向共享(IS)通过在各资源上放置S锁,表明事务的意向是读取层次结构中的部分(而不是全部)底层资源。
意向排它(IX)通过在各资源上放置X锁,表明事务的意向是修改层次结构中的部分(而不是全部)底层资源。
IX是IS的超集。
与意向排它共享(SIX)通过在各资源上放置IX锁,表明事务的意向是读取层次结构中的全部底层资源并修改部分(而不是全部)底层资源。
允许顶层资源上的并发IS锁。
例如,表的SIX锁在表上放置一个SIX锁(允许并发IS锁),在当前所修改页上放置IX锁(在已修改行上放置X锁)。
虽然每个资源在一段时间内只能有一个SIX锁,以防止其它事务对资源进行更新,但是其它事务可以通过获取表级的IS锁来读取层次结构中的底层资源。
独占锁:
只允许进行锁定操作的程序使用,其他任何对他的操作均不会被接受。
执行数据更新命令时,SQLServer会自动使用独占锁。
当对象上有其他锁存在时,无法对其加独占锁。
共享锁:
共享锁锁定的资源可以被其他用户读取,但其他用户无法修改它,在执行Select时,SQLServer会对对象加共享锁。
更新锁:
当SQLServer准备更新数据时,它首先对数据对象作更新锁锁定,这样数据将不能被修改,但可以读取。
等到SQLServer确定要进行更新数据操作时,他会自动将更新锁换为独占锁,当对象上有其他锁存在时,无法对其加更新锁。
2.从程序员的角度看:
分为乐观锁和悲观锁。
乐观锁:
完全依靠数据库来管理锁的工作。
悲观锁:
程序员自己管理数据或对象上的锁处理。
MS-SQLSERVER使用锁在多个同时在数据库内执行修改的用户间实现悲观并发控制
三锁的粒度
锁粒度是被封锁目标的大小,封锁粒度小则并发性高,但开销大,封锁粒度大则并发性低但开销小
SQLServer支持的锁粒度可以分为为行、页、键、键范围、索引、表或数据库获取锁
资源描述
RID行标识符。
用于单独锁定表中的一行。
键索引中的行锁。
用于保护可串行事务中的键范围。
页8千字节(KB)的数据页或索引页。
扩展盘区相邻的八个数据页或索引页构成的一组。
表包括所有数据和索引在内的整个表。
DB数据库。
四锁定时间的长短
锁保持的时间长度为保护所请求级别上的资源所需的时间长度。
用于保护读取操作的共享锁的保持时间取决于事务隔离级别。
采用READCOMMITTED的默认事务隔离级别时,只在读取页的期间内控制共享锁。
在扫描中,直到在扫描内的下一页上获取锁时才释放锁。
如果指定HOLDLOCK提示或者将事务隔离级别设置为REPEATABLEREAD或SERIALIZABLE,则直到事务结束才释放锁。
根据为游标设置的并发选项,游标可以获取共享模式的滚动锁以保护提取。
当需要滚动锁时,直到下一次提取或关闭游标(以先发生者为准)时才释放滚动锁。
但是,如果指定HOLDLOCK,则直到事务结束才释放滚动锁。
用于保护更新的排它锁将直到事务结束才释放。
如果一个连接试图获取一个锁,而该锁与另一个连接所控制的锁冲突,则试图获取锁的连接将一直阻塞到:
将冲突锁释放而且连接获取了所请求的锁。
连接的超时间隔已到期。
默认情况下没有超时间隔,但是一些应用程序设置超时间隔以防止无限期等待
五SQLServer中锁的自定义
1处理死锁和设置死锁优先级
死锁就是多个用户申请不同封锁,由于申请者均拥有一部分封锁权而又等待其他用户拥有的部分封锁而引起的无休止的等待
可以使用SETDEADLOCK_PRIORITY控制在发生死锁情况时会话的反应方式。
如果两个进程都锁定数据,并且直到其它进程释放自己的锁时,每个进程才能释放自己的锁,即发生死锁情况。
2处理超时和设置锁超时持续时间。
@LOCK_TIMEOUT返回当前会话的当前锁超时设置,单位为毫秒
SETLOCK_TIMEOUT设置允许应用程序设置语句等待阻塞资源的最长时间。
当语句等待的时间大于LOCK_TIMEOUT设置时,系统将自动取消阻塞的语句,并给应用程序返回"已超过了锁请求超时时段"的1222号错误信息
示例
下例将锁超时期限设置为1,800毫秒。
SETLOCK_TIMEOUT18003)设置事务隔离级别。
4)对SELECT、INSERT、UPDATE和DELETE语句使用表级锁定提示。
5)配置索引的锁定粒度
可以使用sp_indexoption系统存储过程来设置用于索引的锁定粒度
六查看锁的信息
1执行EXECSP_LOCK报告有关锁的信息
2查询分析器中按Ctrl+2可以看到锁的信息
七使用注意事项
如何避免死锁
1使用事务时,尽量缩短事务的逻辑处理过程,及早提交或回滚事务;
2设置死锁超时参数为合理范围,如:
3分钟-10分种;超过时间,自动放弃本次操作,避免进程悬挂;
3优化程序,检查并避免死锁现象出现;
4.对所有的脚本和SP都要仔细测试,在正是版本之前。
5所有的SP都要有错误处理(通过@error)
6一般不要修改SQLSERVER事务的默认级别。
不推荐强行加锁
解决问题如何对行表数据库加锁
八几个有关锁的问题
1如何锁一个表的某一行
SETTRANSACTIONISOLATIONLEVELREADUNCOMMITTEDSELECT*FROMtableROWLOCKWHEREid=12锁定数据库的一个表
SELECT*FROMtableWITH(HOLDLOCK)
加锁语句:
sybase:
update表setcol1=col1where1=0;
MSSQL:
selectcol1from表(tablockx)where1=0;
oracle:
LOCKTABLE表INEXCLUSIVEMODE;
加锁后其它人不可操作,直到加锁用户解锁,用commit或rollback解锁
几个例子帮助大家加深印象
设table1(A,B,C)
ABCa1b1c1a2b2c2a3b3c31)排它锁
新建两个连接
在第一个连接中执行以下语句
begintranupdatetable1setA='aa'
whereB='b2'
waitfordelay'00:
00:
30'--等待30秒
committran
在第二个连接中执行以下语句
begintranselect*fromtable1whereB='b2'
committran
若同时执行上述两个语句,则select查询必须等待update执行完毕才能执行即要等待30秒
2)共享锁
在第一个连接中执行以下语句
begintranselect*fromtable1holdlock-holdlock人为加锁
whereB='b2'
waitfordelay'00:
00:
30'--等待30秒
committran
在第二个连接中执行以下语句
begintranselectA,Cfromtable1whereB='b2'
updatetable1setA='aa'
whereB='b2'
committran
若同时执行上述两个语句,则第二个连接中的select查询可以执行
而update必须等待第一个事务释放共享锁转为排它锁后才能执行即要等待30秒
3)死锁
增设table2(D,E)
DE
d1e1d2e2
在第一个连接中执行以下语句
begintranupdatetable1setA='aa'
whereB='b2'
waitfordelay'00:
00:
30'
updatetable2setD='d5'
whereE='e1'
committran
在第二个连接中执行以下语句
begintranupdatetable2setD='d5'
whereE='e1'
waitfordelay'00:
00:
10'
updatetable1setA='aa'
whereB='b2'
committran
同时执行,系统会检测出死锁,并中止进程
补充一点:
SqlServer2000支持的表级锁定提示
HOLDLOCK持有共享锁,直到整个事务完成,应该在被锁对象不需要时立即释放,等于SERIALIZABLE事务隔离级别
NOLOCK语句执行时不发出共享锁,允许脏读,等于READUNCOMMITTED事务隔离级别
PAGLOCK在使用一个表锁的地方用多个页锁
READPAST让sqlserver跳过任何锁定行,执行事务,适用于READUNCOMMITTED事务隔离级别只跳过RID锁,不跳过页,区域和表锁
ROWLOCK强制使用行锁
TABLOCKX强制使用独占表级锁,这个锁在事务期间阻止任何其他事务使用这个表
UPLOCK强制在读表时使用更新而不用共享锁
应用程序锁:
应用程序锁就是客户端代码生成的锁,而不是sqlserver本身生成的锁
处理应用程序锁的两个过程
sp_getapplock锁定应用程序资源
sp_releaseapplock为应用程序资源解锁
注意:
锁定数据库的一个表的区别
SELECT*FROMtableWITH(HOLDLOCK)其他事务可以读取表,但不能更新删除
SELECT*FROMtableWITH(TABLOCKX)其他事务不能读取表,更新和删除
1如何锁一个表的某一行
A连接中执行
SETTRANSACTIONISOLATIONLEVELREPEATABLEREADbegintranselect*fromtablenamewith(rowlock)whereid=3waitfordelay'00:
00:
05'
committranB连接中如果执行
updatetablenamesetcolname='10'whereid=3--则要等待5秒
updatetablenamesetcolname='10'whereid3--可立即执行
2锁定数据库的一个表
SELECT*FROMtableWITH(HOLDLOCK)注意:
锁定数据库的一个表的区别SELECT*FROMtableWITH(HOLDLOCK)
其他事务可以读取表,但不能更新删除
SELECT*FROMtableWITH(TABLOCKX)
其他事务不能读取表,更新和删除
并发控制
并发能力是指多用户在同一时间对相同数据同时访问的能力。
一般的关系型数据库都具有并发控制的能力,但是这种并发功能也会对数据的一致性带来危险。
试想若有两个用户都试图访问某个银行用户的记录并同时要求修改该用户的存款余额时,情况将会怎样呢?
我们可以对PowerBuilder中的DataWindow进行设置来进行并发控制。
所谓并发控制就是指在用户数据修改的过程中保证该数据不被覆盖或改变的方式,在下面的例子中我们将看到如何设置DataWindow来控制开发访问。
为了说明问题,我们举这样一个简单的银行系统中的例子,某用户的存款状况如右:
我们假设事情的经过是这样的:
公司的某员工在银行前台取款2,000元,银行出纳查询用户的存款信息显示银行存款余额20,000元;正在这时,另一银行帐户转帐支票支付该帐户5,000元,机器查询也得到当前用户存款20,000元,这时银行的出纳员看到用户存款超过了取款额,就支付了客户2,000元并将用户存款改为18,000元,然后银行的另一名操作员根据支票,将汇入的5,000元加上,把用户的余额改为25,000元,那么数据库管理系统是否可以接受这些修改呢?
在DataWindows的设计中,我们选择菜单Rows|Update…,会出现SpecifyUpdateCharacteristics的设置窗口,在这个窗口中我们设置Update语句中Where子句的生成,以此来进行开发控制。
在这里有三个选项,我们分别看一看在本例中这三个选项的结果:
(1)KeyColumns:
生成的Where子句中只比较表中的主键列的值与最初查询时是否相同来确定要修改的记录。
在上述的例子中,转帐支票的操作将覆盖出纳员作出的修改,这样银行损失两千元。
(2)KeyandUpdateableColumns:
生成的Where子句比较表中主键列和可修改列的值与最初查询时否是相同。
在上例中两次查询出的结果都是有两万余额,当第一个人修改余额时,余额仍是二万元,所以修改成立,而支票转帐操作时余额已不是二万,所以该列不匹配,修改失败。
(3)KeyandModifiedColumns:
Where子句比较主键和将要修改的列,在本例中,结果与KeyandUpdateableColumns的选择相同,因为余额已改变,不再与最初的查询相同,因此仍然不能修改。
让我们作另外一个假设,我们把银行后台作支票转帐操作改为冻结用户存款,即把状态字段的值改为冻结,而且事件发生的次序如下表,那么表中的次序4…前台出纳的修改能不能成立呢:
1.KeyColumns:
Where子句只比较主键值,显然出纳员的修改是允许的。
2.KeyandUpdateableColumns:
生成的Where子句包括比较所有可修改的列,因此出纳修改时Statue字段为冻结与出纳查询时的tive不符,修改失败,同时显示错误信息。
3.KeyandModifiedColumns:
Where子句的比较包括主键和要修改的列,由于本列中修改列仍为20,000元没有变化,所以出纳的修改可以成立。
在本例中,我们可以看到KeyandUpdateableColumns的选项最严格,可以避免出现状态列发生改变时余额作修改的错误,但是这也会禁止我们作一些本当允许的并发修改,如出纳修改存款余额,而业务员修改用户的联系地址等。
因此我们应当根据实际情况,选择适当的Update设置。
根据我们使用数据库的不同,我们还有一些其他的控制并发访问和修改的选择方案,如对数据加锁。
锁是一个用户避免其他用户对指定行作修改的操作。
在结束一个事务如执行commit,rollback,disconnect等语句时自动将锁释放。
如果您使用的DBMS支持锁的操作,在Power-Builder的DataWindow设计时,Select语句可在from子句中加上withholdlock:
即在dataWindow的SQLWindow中,在表窗口的标题处点击右鼠标,弹出菜单的最后一个选项即为Holdlock。
选择该项,生成的SQL语句将在re-trievel()函数执行后将所查询的数据加锁,以避免其他用户的修改访问,直至commit,rollback等事件发生后解锁。
这种方式带来的问题是,当用户查询完数据后可能离开计算机长时间不用,这段时间内其他用户均无法修改数据。
此外有些DBMS如Sybase等不支持行级锁,也就是说当你对某一行查询时更多的行都被上了锁,这就更增加了并发处理的局限性。
另一个值得注意的问题是在多窗口应用中某一个窗口的事务提交将会导致使用一事务中其他数据窗口的查询行解锁,这时修改将可能发生错误。
某些DBMS系统支持一个称作"时间戳(timestamp)"的数据项来控制并发性。
每张表中都有一个时间戳的数据列,当Insert语句或Update语句对数据行作修改时该列自动被修改为当前时间。
当你要作修改时,where子句可检查时间戳列在查询时和修改时两个值是否相符,以此来确保您作出的修改不会覆盖别人的修改,因此这种确认方式与keyandUpdateableColumns选项相同。
即使两个用户对同一行的不同列作修改,后一个修改者也将失败。
在常用的关系型数据库中Sybase和Microsoft的SQLServer支持时间戳的使用。
而在PowerBuilder中,不管用户后台连接何种数据库,只要表中带有timestamp的列名且数据类型为datetime,PB将自动忽略Updatecharacteristics的选项,而在where子句中生成主键和时间戳列的比较。
如果您所用的数据库不支持时间戳但支持触发器,您也可以在表中增加一列整数型的列。
当有对表中某种记录作修改时,该列自动加1。
下列使用的是Watcom数据库,对Shipper表增加Updcnt字段并作两个触发器,这样任何用户或进程试图修改某行记录时,该字段均可发生变化。
对INSERT触发器的编写如下:
DROPTRIGGERINS-SHIPPER'
CREATETRIGGERSHIPPERBEFOREINSERTONSHIPPERREFERENCINGNEWASNewvalueFOREACHROWBEGINSETnewvalue.UpdCnt=newvalue.UpdCnt+1;
END'
同理可编写UPDATE触发器。
在您的PowerBuilder应用之中,除表的主键外,必须再加上这一列作为检测列加入Update语句中的Where子句中,这样再作Update操作时,后台数据库会比较修改时与用户作Retrieve操作时数据是否相等,以确认是否能作修改。
在DataWindows中在SpecifyUpdateCharacteris-tics的对话框的右下角的Uniquekeycolumn(s)中加上Updcnt一项,同时注意whereclause中选择Keycolumns,这样PowerBuilder在构造where子句时就会认为Updcnt亦是表的主键,而成为检测项。
当数据窗口的Update函数被调用后,触发器将修改过记录中的Updcnt列表为新值,为保证下一次修改能够有效,您应当立即作Retrieve()以使DataWindow缓冲区中Updcnt的值与数据库相同。
显然修改后立即查询的代价要比其他任何一种并
发控制的代价要小得多。
不想倒也不觉得,可真的想想似乎好像也不是那么清楚,我的理解大概是这样:
最严格的并发控制:
KeyandUpdateableColumns+UseUpdate
对主键和所有可更新列进行检查:
有任何一个列在提交时发生了变化即告失败,即阻止事物之间修改同一数据。
直接更新方式:
阻止事物内部对主键列进行任何交叉修改(逻辑正确也不行)。
最宽松的并发控制:
KeyColumns+UseDeletethenInsert
仅对主键列进行检查:
允许非主键列数据被不同事物同时修改。
删除后插入方式:
允许事物内部对主键列进行逻辑正确的交叉修改。
死锁预防:
不同数据库的锁机制各不相同,但对应用程序来说,造成死锁的最大可能就是:
没有养成对每个COMMIT的执行结果进行检查的编码习惯,导致提交出错时未能及时ROLLBACK造成死锁。
其实就是这么一个过程,在dwupdate时,他会先检查你所定为key的几列和retrieve时取出的来值是否相同,如果相同则update,不同则报
"rowchangedbetweenretrieveandupdate"这个错误
如果出了这个错你会看到在这个错误框里会有个update语句
update表set你所设要更新的列=:
值
where设定的key=原来的值;
其实讲了那么多,无非就是这么几种情况:
1、在做报表时设置成:
KEYANDMODIFIEDCOLUMNS&&USEUPDATE2、使用SQL语句:
加上LOCK或RowLock.
3、记得UPDATE后要COMMIT或ROLLBACK。
4、运用FOCUS或存储过程记得还要CLOSE。
一般来说,要简单的话,记得这样就可以了:
用DataWindow操作的话:
设置DW的SpecifyUpdateCharacteristics为:
(3)KeyandModifiedColumns
这样,只要你更新的列的值没有变,则大家都可以成功,有效的防止了多用户重叠更新的问题,相当安全,不必使用第二选项.
如果是使用SQL语句的话,就要使用事务,这样就能确保你的修改是完整而且不被别人干扰的.而且不必使用上面各位朋友的复杂的方法,就这