锁是网络数据库中的一个非常重要的概念它主要用于多用户环境下保证数据库完整性和一致性各种大型数据库所采用的锁的基本理论是一致的但在具体实现上各有差别目前大多数数据库管理系统都或多或少具有自我调节自我管理的功能因此很多用户实际上不清楚锁的理论和所用数据库中锁的具体实现
Microsoft SQL Server(以下简称SQL Server)作为一种中小型数据库管理系统已经得到了广泛的应用该系统更强调由系统来管理锁在用户有SQL请求时系统分析请求自动在满足锁定条件和系统性能之间为数据库加上适当的锁同时系统在运行期间常常自动进行优化处理实行动态加锁对于一般的用户而言通过系统的自动锁定管理机制基本可以满足使用要求但如果对数据安全数据库完整性和一致性有特殊要求就必须自己控制数据库的锁定和解锁这就需要了解SQL Server的锁机制掌握数据库锁定方法
锁的多粒度性以及锁升级
数据库中的锁是指一种软件机制用来指示某个用户(也即进程会话下同)已经占用了某种资源从而防止其他用户做出影响本用户的数据修改或导致数据库数据的非完整性和非一致性这儿所谓资源主要指用户可以操作的数据行索引以及数据表等根据资源的不同锁有多粒度(multigranular)的概念也就是指可以锁定的资源的层次SQL Server中能够锁定的资源粒度包括数据库表区域页面键值(指带有索引的行数据)行标识符(RID即表中的单行数据)
采用多粒度锁的重要用途是用来支持并发操作和保证数据的完整性SQL Server根据用户的请求做出分析后自动给数据库加上合适的锁假设某用户只操作一个表中的部分行数据系统可能会只添加几个行锁(RID)或页面锁这样可以尽可能多地支持多用户的并发操作但是如果用户事务中频繁对某个表中的多条记录操作将导致对该表的许多记录行都加上了行级锁数据库系统中锁的数目会急剧增加这样就加重了系统负荷影响系统性能因此在数据库系统中一般都支持锁升级(lock escalation)所谓锁升级是指调整锁的粒度将多个低粒度的锁替换成少数的更高粒度的锁以此来降低系统负荷在SQL Server中当一个事务中的锁较多达到锁升级门限时系统自动将行级锁和页面锁升级为表级锁特别值得注意的是在SQL Server中锁的升级门限以及锁升级是由系统自动来确定的不需要用户设置
锁的模式和兼容性
在数据库中加锁时除了可以对不同的资源加锁还可以使用不同程度的加锁方式即锁有多种模式SQL Server中锁模式包括
.共享锁
SQL Server中共享锁用于所有的只读数据操作共享锁是非独占的允许多个并发事务读取其锁定的资源默认情况下数据被读取后SQL Server立即释放共享锁例如执行查询SELECT * FROM my_table时首先锁定第一页读取之后释放对第一页的锁定然后锁定第二页这样就允许在读操作过程中修改未被锁定的第一页但是事务隔离级别连接选项设置和SELECT语句中的锁定设置都可以改变SQL Server的这种默认设置例如 SELECT * FROM my_table HOLDLOCK就要求在整个查询过程中保持对表的锁定直到查询完成才释放锁定
.修改锁
修改锁在修改操作的初始化阶段用来锁定可能要被修改的资源这样可以避免使用共享锁造成的死锁现象因为使用共享锁时修改数据的操作分为两步首先获得一个共享锁读取数据然后将共享锁升级为独占锁然后再执行修改操作这样如果同时有两个或多个事务同时对一个事务申请了共享锁在修改数据的时候这些事务都要将共享锁升级为独占锁这时这些事务都不会释放共享锁而是一直等待对方释放这样就造成了死锁如果一个数据在修改前直接申请修改锁在数据修改的时候再升级为独占锁就可以避免死锁修改锁与共享锁是兼容的也就是说一个资源用共享锁锁定后允许再用修改锁锁定
.独占锁
独占锁是为修改数据而保留的它所锁定的资源其他事务不能读取也不能修改独占锁不能和其他锁兼容
.结构锁
结构锁分为结构修改锁(SchM)和结构稳定锁(SchS)执行表定义语言操作时SQL Server采用SchM锁编译查询时SQL Server采用SchS锁
.意向锁
意向锁说明SQL Server有在资源的低层获得共享锁或独占锁的意向例如表级的共享意向锁说明事务意图将独占锁释放到表中的页或者行意向锁又可以分为共享意向锁独占意向锁和共享式独占意向锁共享意向锁说明事务意图在共享意向锁所锁定的低层资源上放置共享锁来读取数据独占意向锁说明事务意图在共享意向锁所锁定的低层资源上放置独占锁来修改数据共享式独占锁说明事务允许其他事务使用共享锁来读取顶层资源并意图在该资源低层上放置独占锁
.批量修改锁
批量复制数据时使用批量修改锁可以通过表的TabLock提示或者使用系统存储过程sp_tableoption的table lock on bulk load选项设定批量修改锁
另外SQL Server命令语句操作会影响锁定的方式语句的组合也同样能产生不同的锁定详情如下表
锁沖突及其防止办法
在数据库系统中死锁是指多个用户(进程)分别锁定了一个资源并又试图请求锁定对方已经锁定的资源这就产生了一个锁定请求环导致多个用户(进程)都处于等待对方释放所锁定资源的状态
在SQL Server中系统能够自动定期搜索和处理死锁问题系统在每次搜索中标识所有等待锁定请求的进程会话如果在下一次搜索中该被标识的进程仍处于等待状态SQL Server就开始递归死锁搜索
当搜索检测到锁定请求环时系统将根据各进程会话的死锁优先级别来结束一个优先级最低的事务此后系统回滚该事务并向该进程发出号错误信息这样其他事务就有可能继续运行了死锁优先级的设置语句为
SET DEADLOCK_PRIORITY { LOW | NORMAL}
其中LOW说明该进程会话的优先级较低在出现死锁时可以首先中断该进程的事务另外各进程中通过设置LOCK_TIMEOUT选项能够设置进程处于锁定请求状态的最长等待时间该设置的语句
SET LOCK_TIMEOUT { timeout_period }
其中timeout_period以毫秒为单位
理解了死锁的概念在应用程序中就可以采用下面的一些方法来尽量避免死锁了
()合理安排表访问顺序
()在事务中尽量避免用户干预尽量使一个事务处理的任务少些
()采用髒读技术髒读由于不对被访问的表加锁而避免了锁沖突在客户机/服务器应用环境中有些事务往往不允许读髒数据但在特定的条件下我们可以用髒读
()数据访问时域离散法数据访问时域离散法是指在客户机/服务器结构中采取各种控制手段控制对数据库或数据库中的对象访问时间段主要通过以下方式实现: 合理安排后台事务的执行时间采用工作流对后台事务进行统一管理工作流在管理任务时一方面限制同一类任务的线程数(往往限制为个)防止资源过多占用; 另一方面合理安排不同任务执行时序时间尽量避免多个后台任务同时执行另外 避免在前台交易高峰时间运行后台任务
()数据存储空间离散法数据存储空间离散法是指采取各种手段将逻辑上在一个表中的数据分散到若干离散的空间上去以便改善对表的访问性能主要通过以下方法实现: 第一将大表按行或列分解为若干小表; 第二按不同的用户群分解
()使用尽可能低的隔离性级别隔离性级别是指为保证数据库数据的完整性和一致性而使多用户事务隔离的程度SQL定义了种隔离性级别未提交读提交读可重复读和可串行如果选择过高的隔离性级别如可串行虽然系统可以因实现更好隔离性而更大程度上保证数据的完整性和一致性但各事务间沖突而死锁的机会大大增加大大影响了系统性能
()使用Bound ConnectionsBound connections 允许两个或多个事务连接共享事务和锁而且任何一个事务连接要申请锁如同另外一个事务要申请锁一样因此可以允许这些事务共享数据而不会有加锁的沖突
()考虑使用乐观锁定或使事务首先获得一个独占锁定一个最常见的死锁情况发生在系列号生成器中它们通常是这样编写的
begin tran
select new_id from keytab holdlock
update keytab set new_id=new_id+l
commit tran
如果有两个用户在同时运行这一事务他们都会得到共享锁定并保持它当两个用户都试图得到keytab表的独占锁定时就会进入死锁为了避免这种情况的发生应将上述事务重写成如下形式
begin tran
update keytab set new_id=new_id+l
select new_id from keytab
commit tran
以这种方式改写后只有一个事务能得到keytab的独占锁定其他进程必须等到第一个事务的完成这样虽增加了执行时间但避免了死锁
如果要求在一个事务中具有读取的可重复能力就要考虑以这种方式来编写事务以获得资源的独占锁定然后再去读数据例如如果一个事务需要检索出titles表中所有书的平均价格并保证在update被应用前结果不会改变优化器就会分配一个独占的表锁定考虑如下的SQL代码