理解锁
在一个多用户环境中并发是最重要的问题之一当多个会话向共享的资源读取或写入数据时数据可能丢失它的完整性为了避免出现这种情况每一个称职的RDBMS都实现了并发控制机制对于RDBMS服务器通过各种锁机制来管理并发所有RDBMS(包括MicrosoftAccess)都实现了一些复杂的并发管理机制尽管SQL标准为游标声明了锁定但锁定既不是SQL的一部分也不是一种标准(游标是一种用于逐行访问数据的特殊结构)
Oracle的锁机制可能是最先进也是最复杂的它遵循这样的规则读取和写入进程相互之间不能阻碍对方即使它们正在访问相同(或接近)的数据集在Oracle中每一个会话将接收到一个读一致的数据快照因此即使某些其他进程已经开始修改数据集中的数据但只要并未提交这些修改随后的每一会话将依然能从快照中读取到原来的数据一旦在第一个会话中提交了更改则其他每一个会话也都能看到更改后的数据仅当将修改提交到数据库时才需要锁Oracle自动选择限制最小的锁用户可以选择手动锁定一个资源(例如一个表)在这种情况下根据使用的锁类型其他用户可能依然能够访问这些数据
IBM DB 和Microsoft SQL Server 都使用了锁这两种数据库中的锁允许读取操作锁定写入操作或者写入操作锁定读取操作并发访问数据的问题在某种程度上由于锁的粒度(就是对表页列或行的锁)得以缓解对于只读的查询DDL语句DML查询等都需要获得相应的锁在绝大多数情况下用户无须担心锁的问题RDBMS将自动地为特定的操作选择最合适的锁(或者多个锁)只有在程序逻辑失败的情况下才需要使用SQL语句以手动方式声明锁
MySQL本身仅在表级别上锁定数据它依赖于支持事务的InnoDB(或者IBMDBI)存储引擎和附加的行级别锁定和事务支持PostgreSQL具有健壮的表级和行级锁定机制每一种锁定机制都有良好的粒度
锁定模式
并发的模式可以分为两大类乐观并发和悲观并发这两个术语是不言自明的乐观并发模式的事务假定当多个事务工作于同一个数据集时不太可能发生资源沖突的情况(尽管有可能发生沖突但将任何潜在的问题留给数据库去解决)乐观事务检查当把修改提交给数据库时潜在的沖突并通过重新提交数据来解决沖突的问题(在这种情况下最后提交的数据具有优先权)悲观并发模式的事务设想从一开始就存在沖突并锁定它们试图使用的资源尽管悲观并发模式能确保数据最高级别的一致性但它也是最昂贵的可能会使数据库陷入停顿通常情况下RDBMS既支持乐观并发模式也支持悲观并发模式有时用户可以指定事务使用其中一种并发模式悲观并发模式提供了更好的一致性但必须付出性能的代价
锁的粒度对数据库系统的性能有显着的影响行级别的锁可以增加并发性(它不会阻塞其他事务对表的访问)但也常常引起过多的管理开销降低数据库的性能就系统资源方面而言全表锁定具有较小的开销但代价就是降低了系统的并发性和性能在设计数据库应用程序时应该注意这两个问题
锁用于实现悲观并发模式的事务每一种RDBMS都具有自己的锁级别但它们都是大同小异的通常根据使用资源的方式将其分为共享锁和排他锁
例如下面的语句以排他模式锁定了LIBRARY数据库的BOOKS表
LOCK TABLE books IN EXCLUSIVE MODE;
发出该语句的事务将试图锁定表以排他方式使用但是允许执行SELECT语句如果任何其他进程在该表上保持了一个锁则该事务将被放到一个队列中当接收到优先权时它将获得锁在该事务期间该锁将被一直持有(直到执行了COMMIT语句)在不同的RDBMS中该语句实际的语法可能不尽相同但差别不大
锁机制也带来了一个潜在的问题如果一个已经在表上持有锁的事务试图获得另外一个资源上的锁但是第二个事务已经对该资源进行了锁定就可能发生死锁的情况(请参考下一小节)
某些数据库特别是Microsoft SQL Server 和IBM DB 已经实现了一种称为锁提升的机制该术语指的是将很多个精细的锁转换为更粗放的高级别的锁以减少系统在锁管理上的开销Oracle g/g希望在系统资源上具有更好的并发性因此它从不对锁进行提升另外MySQL(使用InnoDB存储引擎)和PostgreSQL都不支持锁提升机制
所有的锁都是由RDBMS酌情决定的(并非用户配置的)默认的锁是行级锁可以将一个行级锁提升为一个表级锁可以使用前面介绍的LOCKTABLE语句提升锁级别锁提升的阈值则由DBA配置
高粒度的锁(即行级别的锁)是有代价的因为SQL Server需要为行级别的锁定操作分配更多的资源因此它会降低SQL Server的性能另外也增加了死锁发生的可能性
关于各个RDBMS中所支持的不同锁类型之间的比较请参考本书的网站wwwwroxcom或者访问wwwagilitatorcom
处理死锁
发生死锁的典型情形是两个(或多个)会话都在等待获得某个共享资源上的锁但是这两个会话都不能继续执行因为第一个会话在第二个会话要求的资源上有一个锁而第二个会话在第一个会话所要求的某个其他资源上也具有一个锁可以设想这样的情形会话持有资源A并试图访问资源B而会话持有资源B并且试图访问资源A在这种情况下就会发生死锁
通常情况下RDBMS采用这样的办法来解决死锁问题自动地杀掉其中一个进程并回滚该进程已经执行的所有操作
Oracle实现了一种复杂的机制用于执行如下规则读取和写入进程不能相互阻塞对方该规则背后的思想是为每一个进程提供一个一致的数据映像其中不包含未提交的更改尽管如此在Oracle中依然会发生死锁问题并由RDBMS来进行判决在极少的情况下需要手动解决死锁问题选择牺牲一个死锁的进程最常见的死锁类型是ORA(排队死锁)和ORA(库缓存死锁)可以声明NOWAIT子句或者设置会话超时来避免死锁另外还可以使用其他一些技术来避免死锁例如显式锁定和事务的隔离级别通过Oracle的界面也可以手动解决死锁问题
IBM DB运行一个后台进程Deadlock Detector用于发现和解决死锁问题被选定作为死锁牺牲品的会话将被回滚并产生一个特殊的错误(SQLCODESQLSTATE)DB主要选择只读进程作为死锁的牺牲品然后再应用最小开销条件来选择要杀掉的会话如果总是出现死锁的问题IBM建议使用系统监视工具来收集关于死锁情况的信息要么优化系统要么重新设计引起死锁的应用程序
Microsoft SQL Server 采用了一种私有的死锁检测算法并采用与其他RDBMS实现的类似方法来解决死锁问题Microsoft SQL Server 可以自动地处理死锁问题用户也可以通过Enterprise Manager Console手动处理死锁问题通过在会话中设置DEADLOCK_PRIORITY参数Microsoft SQL Server 可以找到一个自愿作为死锁牺牲品的会话(请参考本章之前关于会话的讨论)
PostgreSQL和MySQL试图通过中止其中一个竞争的事务来解决死锁的问题
总而言之避免死锁的最佳实践包括首先就运行较小的事务尽可能快地运行提交命令重构访问表的逻辑减少显式锁定指令的使用等
返回目录SQL实战新手入门
编辑推荐
Oracle索引技术
高性能MySQL
数据仓库与数据挖掘培训视频教程