| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 4419 人关注过本帖
标题:Kendy123456 进来
只看楼主 加入收藏
棉花糖ONE
Rank: 7Rank: 7Rank: 7
等 级:贵宾
威 望:32
帖 子:2987
专家分:0
注 册:2006-7-13
收藏
 问题点数:0 回复次数:21 
Kendy123456 进来
大哥给我们讲讲,锁,事务,和并发的知识
搜索更多相关主题的帖子: 大哥 知识 
2007-01-28 23:01
Kendy123456
Rank: 10Rank: 10Rank: 10
等 级:贵宾
威 望:62
帖 子:2720
专家分:0
注 册:2007-1-3
收藏
得分:0 

锁, 事务 单独出来讲其实都没什么意义 通常情况下引起问题的在于并发时候引起的冲突.

并发是指多个事务同时对同一资源进行操作.
SQL2000以上的版本在事务中是强制使用锁定的.

先讲一下如果不加锁会出现什么问题: (以下摘自SQL帮助)
**********
丢失更新
当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,会发生丢失更新问题。每个事务都不知道其它事务的存在。最后的更新将重写由其它事务所做的更新,这将导致数据丢失。

例如,两个编辑人员制作了同一文档的电子复本。每个编辑人员独立地更改其复本,然后保存更改后的复本,这样就覆盖了原始文档。最后保存其更改复本的编辑人员覆盖了第一个编辑人员所做的更改。如果在第一个编辑人员完成之后第二个编辑人员才能进行更改,则可以避免该问题。

未确认的相关性(脏读)
当第二个事务选择其它事务正在更新的行时,会发生未确认的相关性问题。第二个事务正在读取的数据还没有确认并且可能由更新此行的事务所更改。

例如,一个编辑人员正在更改电子文档。在更改过程中,另一个编辑人员复制了该文档(该复本包含到目前为止所做的全部更改)并将其分发给预期的用户。此后,第一个编辑人员认为目前所做的更改是错误的,于是删除了所做的编辑并保存了文档。分发给用户的文档包含不再存在的编辑内容,并且这些编辑内容应认为从未存在过。如果在第一个编辑人员确定最终更改前任何人都不能读取更改的文档,则可以避免该问题。

不一致的分析(非重复读)
当第二个事务多次访问同一行而且每次读取不同的数据时,会发生不一致的分析问题。不一致的分析与未确认的相关性类似,因为其它事务也是正在更改第二个事务正在读取的数据。然而,在不一致的分析中,第二个事务读取的数据是由已进行了更改的事务提交的。而且,不一致的分析涉及多次(两次或更多)读取同一行,而且每次信息都由其它事务更改;因而该行被非重复读取。

例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题。

幻像读
当对某行执行插入或删除操作,而该行属于某个事务正在读取的行的范围时,会发生幻像读问题。事务第一次读的行范围显示出其中一行已不复存在于第二次读或后续读中,因为该行已被其它事务删除。同样,由于其它事务的插入操作,事务的第二次或后续读显示有一行已不存在于原始读中。

例如,一个编辑人员更改作者提交的文档,但当生产部门将其更改内容合并到该文档的主复本时,发现作者已将未编辑的新材料添加到该文档中。如果在编辑人员和生产部门完成对原始文档的处理之前,任何人都不能将新材料添加到文档中,则可以避免该问题。

*******

SQL使用的锁定级别包括: 行,键(索引),页,组(8页),表, 数据库. 锁定模式包括共享,更新,排它,意向,架构,大容量更新.
这些锁定信息是我们无法用代码控制的,系统会自动选择. 所以在这里先不介绍(SQL帮助上也可以查到详细信息).
我们可以在系统表syslockinfo中查看当前所有锁定的信息,也可以用sp_lock查看锁的详细信息.

稍后继续


2007-01-29 10:19
Kendy123456
Rank: 10Rank: 10Rank: 10
等 级:贵宾
威 望:62
帖 子:2720
专家分:0
注 册:2007-1-3
收藏
得分:0 

前面提到大部分锁定信息是SQL自动判定的,那么我们用代码可以控制什么呢?
1.我们可以设置事务隔离等级
事务准备接受不一致数据的级别称为隔离级别。隔离级别是一个事务必须与其它事务进行隔离的程度。较低的隔离级别可以增加并发,但代价是降低数据的正确性。相反,较高的隔离级别可以确保数据的正确性,但可能对并发产生负面影响。
语法
SET TRANSACTION ISOLATION LEVEL
{ READ COMMITTED
| READ UNCOMMITTED
| REPEATABLE READ
| SERIALIZABLE
}

参数
READ COMMITTED

指定在读取数据时控制共享锁以避免脏读,但数据可在事务结束前更改,从而产生不可重复读取或幻像数据。该选项是 SQL Server 的默认值。

READ UNCOMMITTED

执行脏读或 0 级隔离锁定,这表示不发出共享锁,也不接受排它锁。当设置该选项时,可以对数据执行未提交读或脏读;在事务结束前可以更改数据内的数值,行也可以出现在数据集中或从数据集消失。该选项的作用与在事务内所有语句中的所有表上设置 NOLOCK 相同。这是四个隔离级别中限制最小的级别。

REPEATABLE READ

锁定查询中使用的所有数据以防止其他用户更新数据,但是其他用户可以将新的幻像行插入数据集,且幻像行包括在当前事务的后续读取中。因为并发低于默认隔离级别,所以应只在必要时才使用该选项。

SERIALIZABLE

在数据集上放置一个范围锁,以防止其他用户在事务完成之前更新数据集或将行插入数据集内。这是四个隔离级别中限制最大的级别。因为并发级别较低,所以应只在必要时才使用该选项。该选项的作用与在事务内所有 SELECT 语句中的所有表上设置 HOLDLOCK 相同。

可以通过指定表级锁定提示来替代单个 SELECT 语句的隔离级别。指定表级锁定提示不会影响会话中的其它语句。

2.可以设置表级锁定

对表操作的SQL语句可以单独设置锁定信息

HOLDLOCK 将共享锁保留到事务完成,而不是在相应的表、行或数据页不再需要时就立即释放锁。HOLDLOCK 等同于 SERIALIZABLE。
NOLOCK 不要发出共享锁,并且不要提供排它锁。当此选项生效时,可能会读取未提交的事务或一组在读取中间回滚的页面。有可能发生脏读。仅应用于 SELECT 语句。
PAGLOCK 在通常使用单个表锁的地方采用页锁。
READCOMMITTED 用与运行在提交读隔离级别的事务相同的锁语义执行扫描。默认情况下,SQL Server 2000 在此隔离级别上操作。
READPAST 跳过锁定行。此选项导致事务跳过由其它事务锁定的行(这些行平常会显示在结果集内),而不是阻塞该事务,使其等待其它事务释放在这些行上的锁。READPAST 锁提示仅适用于运行在提交读隔离级别的事务,并且只在行级锁之后读取。仅适用于 SELECT 语句。
READUNCOMMITTED 等同于 NOLOCK。
REPEATABLEREAD 用与运行在可重复读隔离级别的事务相同的锁语义执行扫描。
ROWLOCK 使用行级锁,而不使用粒度更粗的页级锁和表级锁。
SERIALIZABLE 用与运行在可串行读隔离级别的事务相同的锁语义执行扫描。等同于 HOLDLOCK。
TABLOCK 使用表锁代替粒度更细的行级锁或页级锁。在语句结束前,SQL Server 一直持有该锁。但是,如果同时指定 HOLDLOCK,那么在事务结束之前,锁将被一直持有。
TABLOCKX 使用表的排它锁。该锁可以防止其它事务读取或更新表,并在语句或事务结束前一直持有。
UPDLOCK 读取表时使用更新锁,而不使用共享锁,并将锁一直保留到语句或事务的结束。UPDLOCK 的优点是允许您读取数据(不阻塞其它事务)并在以后更新数据,同时确保自从上次读取数据后数据没有被更改。
XLOCK 使用排它锁并一直保持到由语句处理的所有数据上的事务结束时。可以使用 PAGLOCK 或 TABLOCK 指定该锁,这种情况下排它锁适用于适当级别的粒度


我认为对前面那个贴的问题, Insert的时候设置HoldLock应该可以锁定该行直到整个事务结束, 以确保insert之后的select不会发生被人更改了标识列的情况.

吃饭了 一会继续讨论


2007-01-29 12:29
棉花糖ONE
Rank: 7Rank: 7Rank: 7
等 级:贵宾
威 望:32
帖 子:2987
专家分:0
注 册:2006-7-13
收藏
得分:0 

打断你一下,我觉得holdlock不能解决问题,insert的时候,系统对表加的是意向锁,在事务没结束过程中意向锁没有被释放,所以我觉得用holdlock多余的,请赐教


26403021 sql群 博客 blog./user15/81152/index.shtml
2007-01-29 13:04
bygg
Rank: 16Rank: 16Rank: 16Rank: 16
来 自:乖乖的心中
等 级:版主
威 望:241
帖 子:13555
专家分:3076
注 册:2006-10-23
收藏
得分:0 

[QUOTE]
<%
'游标类型
Const adOpenForwardOnly = 0
Const adOpenKeyset = 1
Const adOpenDynamic = 2
Const adOpenStatic = 3

'加锁类型
Const adLockReadOnly = 1
Const adLockPessimistic = 2
Const adLockOptimistic = 3
Const adLockBatchOptimistic = 4
>%

<% Set Conn = Server.CreateObject('ADODB.Connection') >%
<% Set RSMov = Server.CreateObject('ADODB.RecordSet') >%
<% Conn.Open '数据源名称', 'sa', '' >%
<% RSMov.Open sqlMov, Conn, adOpenKeyset, adLockReadOnly >%
游标使用时是比较灵活的,它有时用来描述一个记录集,有时又是用来描述当前记录集
中某一条记录的指针。游标主要是用来建立一个关系数据库中行/列关系的一种SQL可利
用的访问格式。与游标有关系的技术术语还有一个叫Bookmark的。如果你选择的游标方式
支持Bookmarks。数据库将提供有关记录数目的强大功能。
在上面写出的那么多游标方式中,adOpenDynamic是没有太的用处的,虽然它提供实时显示
数据库中的记录的所有更新操作的功能,但是因为并不是所有的数据库都支持该游标方式,
没有移植性的游标方式对当前错综复杂的数据库来说真是用处不大。
在实际的编程中,我相信大家使用得最频繁的是adOpenStatic方式,当然这种方式的缺点是
不能够就、实时反应出数据库中内容改变时的状况。如果要想看到数据库被其它用户改变的
状况,可使用adOpenKeyse方式(但是它只能够反应出被编辑的改变情况,也就是说不能够
反映出新增和删除记录的改变情况。)
其实上面的内容大家一般都可以在微软的技术参考资料中找到,下面来说说在使用这些游标
方式和加锁方式时要注意到的问题。
1。首先要注意到的是这两种方式在混合使用时的问题,就是说你同时设置游标方式和加锁方式。
除非你是在使用Access数据库,一般而言当你混合使用时是并不能够得到你预期想要的
游标方式和加锁方式的。例如,如果你同时将游标设置为adOpenStatic方式,而将加锁设置为
adLockOptimistic,你将得不到adOpenStatic方式的游标,你这时使用的游标方式将是
adOpenKeyset,也就是说你使用ADO的话,它将返回adOpenKeyset的游标。
2。其次,游标和加锁的混合使用还会导致ADO返回的不是你想要的加锁方式,ADO会改变你的加锁
方式。例如,在默认状态下游标方式是adOpenForwardOnly,在使用这种游标方式的同时如果
你使用的加锁方式为-1(就是让数据源来判断加锁方式)或则adLockReadOnly,那么这种混合方式
基本上不支持RecordSet的任何方法,也就是说RecordSet的任何方法将返回False
(你的recordcount,absoultpage,addnew,delete,update等都会返回-1,-1就是表示不支持该属性),
但是这时如果你使用的是adOpenForwardOnly游标方式和其它的加锁方式混合,它反而
会支持填加,删除和更新。

[/QUOTE]---CSDN


飘过~~
2007-01-29 13:07
Kendy123456
Rank: 10Rank: 10Rank: 10
等 级:贵宾
威 望:62
帖 子:2720
专家分:0
注 册:2007-1-3
收藏
得分:0 

总结一下 设定事务隔离级别是我们设置数据安全程度的主要手段

隔离级别 脏读 非重复读取 幻像
未提交读 是 是 是
提交读 否 是 是
可重复读 否 否 是
可串行读 否 否 否

而一般情况下 是不推荐设置表锁定的, 特别是低于事务隔离级别的表锁定. 例如在SERIALIZABLE 级别的事务中, 对表操作使用NOLOCK.

于并发相关的另外一个问题是死锁.
死锁是指2个以上的事务之间, 自己锁定了某表,而等待对方释放其它表的锁定这样的死循环.
举个例子:
我们有一个存储过程Pr_PostDataToDatabase
它包含的代码如下:
if @Event = 'AddUser'
begin
insert into table User ...
...
insert into table PersonalInfo ...
end

if @Event ='UpdatePersonalInfo'
begin
Update table User...
....
Update table PersonalInfo ...

end
if @Event = 'DeleteUser'
Begin
Delete PesonalInfo where ...
Delete User where ....
End
...

我们设想,当有2个事务调用这个存储过程对同一个人进行操作, 一个进行Update, 一个进行Delete. 对于Update的事务, 先update了table User并锁定, 然后试图Update PersonalInfo 表的时候, 发现该行已经被事务2锁定了, 于是该事务被阻塞, 并开始等待事务2提交后解除对PersonalInfo表的锁定; 同样,事务2先Delete了 PersonalInfo 表后, 试图去Delete User表的时候, 发现该行已经被事务1锁定了, 于是事务2被阻塞并等待事务1提交以对User表解锁. 这样2个事务之间就陷入了死锁, 谁也无法提交. 并且这个人记录在这2张表内也被死锁了. 这是一个典型的访问顺序问题引起的死锁. 所以. 我们写代码的时候, 要遵循这样一个原则: 在任何事务当中, 都以同样的顺序访问数据表. 这样可以大大降低死锁的可能性.相对于上面的例子, 就是如果3个event中, 对User表的访问顺序永远都排在PersonalInfo的前面, 那么前面说的死锁就不可能发生.
不过实际上, 完全做到所有事务中顺序一致是不可能的. 假如User和PersonalInfo表存在Foreign Key, 比如UserID, 那么Add User时的Insert和Delete User时候的Delete顺序就不能一样的. 这种时候为了避免死锁, 就可能会对事务使用低隔离级别以避免锁定争夺, 对于可能发生的脏读,幻读 就不得不用代码来补救(比如Raise Error).
有一种情况比较特殊, 就是绑定连接. 事务1可以通过先执行 sp_getbindtoken获得绑定令牌(BindToken)字符串,而后执行 sp_bindsession, 创建一个绑定连接. 事务2通过sp_bindsession @TokenString可以加入到这个绑定连接当中, 共享事务1的所有锁而不会引发冲突. 这方面我是没有用过, 有兴趣的可以上网搜一下, 可能分布氏数据库开发当中会用到的多一些.


2007-01-29 14:44
Kendy123456
Rank: 10Rank: 10Rank: 10
等 级:贵宾
威 望:62
帖 子:2720
专家分:0
注 册:2007-1-3
收藏
得分:0 
以下是引用jinyuzhang在2007-1-29 13:04:32的发言:

打断你一下,我觉得holdlock不能解决问题,insert的时候,系统对表加的是意向锁,在事务没结束过程中意向锁没有被释放,所以我觉得用holdlock多余的,请赐教

事务在未提交前 加的意向锁和SQL语句本身产生的锁是不冲突的. 意向锁是表级的, 表示整个事务目前对表的锁定意向; SQL语句指定的表锁定实际上应该是行锁定, 仅仅在锁定过多 达到升级门限的时候才会自动被SQL server升级为页锁定或者表锁定. 而且 判定事务阻塞的时候 意向锁并不是判定的依据.(它只能判定事务不阻塞)
一个insert语句下来, 这个表就会有一个意向排它锁, 并且不被释放. 同时会产生一个排它锁, 并在insert完成之后释放. (否则就不存在幻读了) 而HOLDLock是声明在insert完成之后也维持这个行级的排他锁(仅锁定insert产生的行)不释放,要整个事务提交的时候才释放. 这是我的理解, 我觉得声明为holdlock实际上就等于把这一行的事务隔离级别单独设置成了可串行读, 在这个事务结束前, 对于其它事务而言这行数据将是不可访问的.

事实上锁定的问题除了专业人士, 很少有人专门去研究, 因为太难调试和测试. 我的理解也不一定就对.

[此贴子已经被作者于2007-1-29 15:11:17编辑过]


2007-01-29 15:09
棉花糖ONE
Rank: 7Rank: 7Rank: 7
等 级:贵宾
威 望:32
帖 子:2987
专家分:0
注 册:2006-7-13
收藏
得分:0 
insert就算完成了也不会释放行级排它锁,只有select语句默认情况下才会释放共享锁,我觉得holdlock和select使用才有意义,相当于repeatable read,可以避免幻读

[此贴子已经被作者于2007-1-29 15:18:14编辑过]



26403021 sql群 博客 blog./user15/81152/index.shtml
2007-01-29 15:17
Kendy123456
Rank: 10Rank: 10Rank: 10
等 级:贵宾
威 望:62
帖 子:2720
专家分:0
注 册:2007-1-3
收藏
得分:0 
并发控制的专业流程我也不清楚, 我可以谈一下我目前开发的系统中使用的并发控制模式.
在用户通过访问了数据库记录后, 如果后续的操作牵涉到写操作(insert ,update 和 delete), 我们在一张专门的系统设置表中给它的主键打上锁定标识; 如果存在外键则访问到主表给主表记录打上锁定; 在操作完成之后则解除锁定; 期间其它的会话如果企图访问该行将会程序判定拒绝. 产生的问题在于如果操作非正常结束,那么锁定标识会一致存在, 所以管理员界面中我们有一个锁定列表让管理员可以手工解除锁定. 当然, 如果正常锁定期间, 管理员手工解除锁定, 仍然有可能引发冲突和异常.所以错误判定和异常处理仍然是不可缺少的(定义一大堆的RaiseError和对应操作确实是很让人头疼的事情)

2007-01-29 15:25
Kendy123456
Rank: 10Rank: 10Rank: 10
等 级:贵宾
威 望:62
帖 子:2720
专家分:0
注 册:2007-1-3
收藏
得分:0 

如果Insert之后会自动维持行级的排它锁, 那么其它事务将不能访问到这行数据, 那前面那张贴的问题就不存在了! 也不会存在任何幻读的问题了.

我补充下前面我自己的观点, insert的时候会产生一个排他锁, 在insert完成之后就释放了. 这个排它锁存在的意义在于, 假如insert的是大批量的数据(比如插入5页),已经插入了3页, 在后2页数据插入的时候, 这个锁禁止其它事务访问前3页的数据. 而使用HoldLock应该是在插入完成之后,保持这个锁;( 或者释放排它锁,但保持一个共享锁, 允许数据被访问但不允许它们被修改, 这个要测试才能得出结论了) 直到事务结束.


我此这个观点的依据之一是:

TABLOCK 使用表锁代替粒度更细的行级锁或页级锁。在语句结束前,SQL Server 一直持有该锁。但是,如果同时指定 HOLDLOCK,那么在事务结束之前,锁将被一直持有。

[此贴子已经被作者于2007-1-29 15:43:45编辑过]


2007-01-29 15:37
快速回复:Kendy123456 进来
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.015821 second(s), 7 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved