SQL SERVER中事务和锁的简要理解

来源:互联网 发布:网络为什么使人抑郁 编辑:程序博客网 时间:2024/06/03 18:45

前言

阅读本文前建议先对事务有过基本的了解,有使用过事务,听过“锁”这个概念,希望快速了解这些概念并应用到实际开发中去。

对于事务和锁的详细解说可以参考http://blog.csdn.net/happyflystone/article/list/1中的事务和锁系列,本文只是试图用最简单的语言(甚至不太精准)来帮助初学者理解锁和事务的概念。若要更加精准、全面和深入的概念请自行阅读相关资料。

什么?你说你没用过事务?没用过锁?

其实你一直在用,数据库已经默认在使用了,只是你没感觉到而已。

其实默认情况下,你的每一个增删改查,修改数据库对象,数据库都会自动使用使用事务,同时就用到了锁,数据库会根据你的操作自动使用不同的锁。数据库默认使用“提交读”级别的隔离级别。

下面先讲锁。

操作资源时先锁住资源。锁的作用是隔离数据库的操作,防止并发访问下的数据错乱(具体有哪些错乱请参见http://blog.csdn.net/happyflystone/article/details/4604573)。

也就是说,在操作资源时,不准其他人来捣乱,要把其他人隔离到一边等着。

上面讲的“操作"包括增,删,改,查,修改对象等操作。

我们在做不同的操作时,数据库会自动使用锁,先锁住资源。针对不同的访问可以有不同的锁。

以下是几项最基本的锁(详情请参见MSDN:锁定 [SQL Server], 模式)。

1、   读取数据时的“共享锁”。

在读数据时,若还没读完,其他人不要来修改数据,但其他人可以一起读数据。

2、   更新数据时用“更新锁”。

其实更新数据分为两个过程,第一个过程是读取待修改的行,第二个过程是正式修改数据。

在更新数据时,可以想象有很多行数据要一行一行修改,若还全部没修改完,其他人就不要来读数据,因为读到的数据也是不完整的,除非明确指示愿意读这些脏数据(使用with(nolock)提示);同时其他人绝对不允许中途改数据,至少等我改完了再来改。另外,在更新数据时,表结构也不能改,要是把我要更改的字段都删掉了那我还改什么!要么在我操作前就删除,要么等我操作以后再删。

3、   删除与插入数据时会有“排它锁”.

类似于更新数据,在删除和插入期间,不允许别人来读数据,,除非你明确指示愿意读这些脏数据(使用with(nolock)提示。同时,不管你愿不愿意,都绝对不允许来中途改我的数据。要等我全部改完了其他才能操作。

4、   修改视图,函数等数据对象时会有“架构锁”

修改数据库架构时也不允许其他人来操作。比如我正在删除一个表,你还想来读这个表的数据?明显不可能。

5、   上述锁在真正生效前还会有“意向锁”,就是锁资源前先发布声明:“我要锁资源了,其他人在外边等着,不要强行进来和我抢资源!”,意向锁可以和前面的基本锁进行组合。意向锁的好处当然就是事先通知其他人,免得其他人强行闯进来抢资源,再被暴力赶出去,因为这种“强行抢”和“暴力赶”的成本不低。

锁针对的是数据库资源,哪些资源会被数据库“锁”呢?最基本的有数据行,数据页,数据表,数据文件,甚至整个数据库(详情请参见MSDN:锁定 [SQL Server], 可锁定资源)。

上面讲完了各种基本锁的基础概念,大概对锁有了一个了解,那在我们实际的开发过程中如何使用呢?光看猪跑不吃猪肉没意思吧?

前面已经说到数据库会自动使用锁,而实际上我们可以用一些提示信息(参见MSDN:表提示)指示在一条SQL语句中,数据库使用哪种锁,但是需要事先声明的是,提示终归只是提示,你无法强制数据库做不符合数据规则的事。

锁类型关键字示例说明不加锁NolockSelect * from table with(nolock)不管别人怎么锁住了数据,我都要读出来。但写数据的锁定规则不变。共享锁HoldLockSelect * from table with(HoldLock) 更新锁UpdLockSelect * from table with(UpdLock) 跳过锁ReadPastSelect * from table with(ReadPast) 排它锁XLockSelect * from table with(XLock) 

接下来讲事务。

事务

简单来说,在使用过程中,事务就是把许多SQL语句打包成一个整体执行,这些语句要么全部成功,要么全部失败。

当然,上面这句只描述了事务的“原子性”(为什么叫“原子”?原子在化学反应中被认为是不可再分割的最小单位),事实上事务还有其他特性(一致性,隔离性,持久性),这里重点讲隔离性。

 

前面讲锁的时候就提到了“隔离”,那事务的隔离与锁的隔离有什么区别呢?(专家们不要笑,这问题曾经困扰了我许久,概念太多,很绕的)

事实上事务的隔离就是锁的隔离,事务的隔离是通过锁来实现的,事务只是可以设置隔离的级别,而不同的隔离级别只是控制了锁的粒度(可以暂时粗暴地把“粒度”一词理解为“力度”或“级别”),或者说,发生事务时使用什么锁,锁什么资源,锁多久(这个说法不是完全地精准,但可先粗略地这样理解)。

事务有以下几种基本的隔离级别(详情请参见MSDN:锁定 [SQL Server], 隔离级别)

隔离级别

使用的锁

锁住的资源

锁的时间

不能解决的问题

未提交读无锁不锁资源无别的事务还未更新完数据你就能直接读出来,脏读已提交读共享锁仅读取的行访问完就释放你刚读取完数据行,再读时,原来行中的数据就不一样了,这叫不可重复读可重复读共享锁仅读取的行到事务结束你刚读取完数据行,尽管原来读到的数据不会改,但再读时就可能多了新行了(不会少行哦!),这叫幻像读。可串行化共享锁整个SQL语句可能涉及的范围,包括后面插入的行。到事务结束所有问题都解决了,不会有脏读,不可重复读和幻像读

在上面表中你或许已经发现了一些问题。

1、  每一个隔离级别都是解决上一个隔离级别中未能解决的问题。

2、  不同的隔离级别都只是“共享锁”,也就是说只影响读数据?

答:是的,如你所见。隔离级别控制的就是读数据。

3、  那更新数据呢?为什么没有脏写?难道事务的锁不管吗?

答:不管你用什么隔离级别,同一个事务中,只要有更新或删除数据,这个排它锁就会持续到最后才释放。也就是说,不管如何隔离,哪怕是使用“未提交读”,事务都会保证不脏写。

4、  不可重复读和幻想读到底有什么区别?看上去貌似是一样的。

答:如表格中所述,不可重复读,只是指,在同一个事务内,已经读取到的数据行在第二次读取时行内的数据发生了变化。而幻像读是指,已经读取到了数据,第二次读时,就多出新行了。这两种级别是不一样的,一种是修改行,一种是新增行,尽管差别小,但仍是两个级别。

接下来讲讲如何在开发中使用事务及隔离级别。

隔离级别关键字示例说明未提交读READ UNCOMMITTED

SET TRANSACTIONISOLATIONLEVELREADUNCOMMITTED

Begin tran

Select *fromsysobject

scommit

 已提交读READ COMMITTED

SET TRANSACTIONISOLATIONLEVELREADCOMMITTED

Begin tran

Select *fromsysobjects

commit 

 可重复读REPEATABLE READ

SET TRANSACTIONISOLATIONLEVELREPEATABLEREAD

Begin tran

Select *fromsysobject

scommit 

 可串行化SERIALIZABLE

SET TRANSACTIONISOLATIONlevelSERIALIZABLE

Begin tran

Select *fromsysobjects

commit

 

其实从上面的示例语句差异来看,都只是Read后的关键字不一样,也就表示事务隔离级别就是针对读数据的。

那用事务就是为了锁住读数据,即隔离数据吗?

当然不是,隔离只是事务的一个特性。我们平时用事务多用的是事务的“原子性”,也就是一个事务可以有多批语句,可以把这些语句当作一个整体来执行,并利用事务的”持久性",一次性提交或回滚所有的操作。