锁是防止其他事务访问指定的资源控制、实现并发控制的一种主要手段。为了提高系统的性能,加快事务的处理速度,缩短事务的等待时间,应该使锁定的资源最小化。为了控制锁定的资源,应该首先了解系统的空间管理。在SQL Server系统中,最小的空间管理单位是页,一个页有8K。所有的数据、日志、索引都存放在页上。另外,使用页有一个限制,这就是表中的一行数据必须在同一个页上,不能跨页。页上面的空间管理单位是簇,一个簇是8个连续的页。表和索引的最小占用单位是簇。数据库是由一个或者多个表或者索引组成,即是由多个簇组成。SQL Server系统的空间管理结构示意图如图3所示。
图3 SQL Server空间管理
为了优化系统的并发性,应该根据事务的大小和系统活动的程度,锁定不同的资源。也就是说,既可以锁定比较大的资源,也可以锁定比较小的资源。在SQL Server系统中,已经比较完善地实现了这些要求。在SQL Server 7.0中,可以锁定的资源有多种,这些可以锁定的资源分别是行、页、簇、表和数据库,他们对应的锁分别是行级锁、页级锁、簇级锁、表级锁和数据库级锁。在如图4所示的结构中,数据行存放在页上,页存放在簇上,一个表有若干个簇组成,而若干个表组成了数据库。在这些可以锁定的资源中,最基本的资源是行、页和表,而簇和数据库是特殊的可以锁定的资源。
图4 表、页、行的结构图
行是可以锁定的最小空间。在SQL Server 7.0中,实现了行级锁。行级锁就是指事务在操纵数据的过程中,锁定一行或者若干行数据,其他事务不能同时处理这些行的数据。行级锁占用的数据资源最少,所以在事务的处理过程中,允许其他事务继续操纵同一个表或者同一个页的其他数据,大大降低了其他事务等待处理的时间,提高了系统的并发性。页级锁是一种最优锁,因为行级锁不可能出现数据既被占用又没有使用的浪费现象。在图5中,椭圆形表示行级锁占用的数据,而椭圆形之外的其他数据仍然可以由其他事务使用。行级锁是SQL Server 7.0的重要特征,它的引入引起了数据存储引擎的改变。
图5 行级锁
页级锁是指在事务的操纵过程中,无论事务处理数据的多少,每一次都锁定一页,在这个页上的数据不能被其他事务操纵。在SQL Server 7.0以前,使用的是页级锁。页级锁锁定的资源比行级锁锁定的数据资源多。在页级锁中,即使是一个事务只操纵页上的一行数据,那么该页上的其他数据行也不能被其他事务使用。因此,当使用页级锁时,会出现数据的浪费现象,也就是说,在同一个页上会出现数据被占用却没有使用的现象。在这种现象中,数据的浪费最多不超过一个页上的数据行。在图6中,圆形区表示一个页级锁,在这个圆形区内,只有一个事务可以可以使用圆形区中的数据,其他事务只能使用圆形区以外的数据。
图6 页级锁
簇级锁是一种特殊类型的锁,只能用在一些特殊的情况下。簇级锁就是指事务占用一个簇,这个簇不能同时被其他事务占用。例如在创建数据库和创建表时,系统分配物理空间时使用这种类型的锁。系统是按照簇分配空间的。当系统分配空间时,使用簇级锁,防止其他事务同时使用同一个簇。当系统完成分配空间之后,就不再使用这种类型的簇级锁。特别是,当涉及到对数据操作的事务时,不使用簇级锁。簇级锁的结构如图7所示。椭圆形区域表示簇级锁占用的数据,其他事务只能使用该簇以外的其他簇。
图7 簇级锁
表级锁也是一个非常重要的锁。表级锁是指事务在操纵某一个表的数据时,锁定了这个数据所在的整个表,其他事务不能访问该表中的其他数据。当事务处理的数据量比较大时,一般使用表级锁。表级锁的特点是使用比较少的系统资源,但是却占用比较多的数据资源。与行级锁和页级锁相比,表级锁占用的系统资源例如内存比较少,但是占用的数据资源却是最大。在表级锁时,有可能出现数据的大量浪费现象,因为表级锁锁定整个表,那么其他的事务都不能操纵表中的其他数据。这样,会延长其他事务等待处理的时间,降低系统的并发性能。表级锁的结构示意图如图8所示,椭圆形表示表级锁。
图8 表级锁
数据库级锁是指锁定整个数据库,防止任何用户或者事务对锁定的数据库进行访问。数据库级锁是一种非常特殊的锁,它只是用于数据库的恢复操作过程中。这种等级的锁是一种最高等级的锁,因为它控制整个数据库的操作。只要对数据库进行恢复操作,那么就需要设置数据库为单用户模式,这样系统就能防止其他用户对该数据库进行各种操作。数据库级锁的结构示意图如图9所示。严格地说,数据库级锁不是一种锁,而是一种类似锁的一种单用户模式机制。但是,这种单用户模式机制非常类似锁机制,因此也可以把这种单用户模式称为数据库级锁。
图9 数据库级锁
锁的类型和其兼容性
锁定资源的方式有两种基本形式,一种形式是读操作要求的共享锁,另一种形式是写操作要求的排它锁。除了这两种基本类型的所,还有一些特殊情况的锁,例如意图锁、修改锁和模式锁。在这些各种类型的锁中,有些类型的锁之间是可以兼容的,有些类型的锁之间是不兼容的。
共享锁允许并行事务读取同一种资源,这时的事务不能修改访问的数据。当使用共享锁锁定资源时,不允许修改数据的事务访问数据。当读取数据的事务读完数据之后,立即释放所占用的资源。一般地,当使用SELECT语句访问数据时,系统自动对所访问的数据使用共享锁锁定。对于那些修改数据的事务,例如使用INSERT、UPDATE和DELETE语句,系统自动在所修改的事务上放置排它锁。排它锁就是在同一时间内只允许一个事务访问一种资源,其他事务都不能在有排它锁的资源上访问。在有排它锁的资源上,不能放置共享锁,也就是说不允许可以产生共享锁的事务访问这些资源。只有当产生排它锁的事务结束之后,排它锁锁定的资源才能被其他事务使用。
除了上面的基本锁以外,根据不同的情况,SQL Server还可以使用一些其他类型的锁。这些特殊类型的锁包括意图锁、修改锁和模式锁。
系统使用意图锁来最小化锁之间的冲突。意图锁建立一个锁机制的分层结构,这种结构依据锁定的资源范围从低到高依次是行级锁层、页级锁层和表级锁层。意图锁表示系统希望在层次低的资源上获得共享锁或者排它锁。例如,放置在表级上的意图锁表示一个事务可以在表中的页或者行上放置共享锁。在表级上设置共享锁防止以后另外一个修改该表中页的事务在包含了该页的表上放置排它锁。意图锁可以提高性能,这是因为系统只需要在表级上检查意图锁,确定一个事务能否在哪个表上安全地获取一个锁,而不需要检查表上的每一个行锁或者页锁,确定一个事务是否可以锁定整个表。意图锁有三种类型,即意图共享锁、意图排它锁和使用意图排它的共享锁。意图共享锁表示读低层次资源的事务的意图,把共享锁放在这些单个的资源上。意图排它锁表示修改低层次的事务的意图,把排它锁放在这些单个资源上。意图排它锁包括意图共享锁,它是意图共享锁的超集。使用意图排它的共享锁表示允许并行读取顶层资源的事务的意图,并且修改一些低层次的资源,把意图排它锁放在这些单个资源上。例如,表上的一个使用意图排它的共享锁把共享锁放在表上,允许并行读取,并且把意图排它锁放在将要修改的页上,把排它锁放在修改的行上。每一个表一次只能有一个使用意图排它的共享锁因为表级共享锁阻止对表的任何修改。使用意图排它的共享锁是共享锁和意图排它锁的组合。
当系统将要修改一个页时,使用修改锁。在系统修改该页之前,系统自动地把这个修改页锁上升到排它页锁,防止锁之间发生冲突。当第一次读取页时,在修改操作的开始阶段,获得修改锁。修改锁与共享锁是兼容的。如果该页被修改了,那么修改锁上升到排它锁。
模式锁保证当表或者索引被另外一个会话参考时,不能被删除或者修改其结构模式。SQL Server系统提供了两种类型的模式锁:模式稳定锁和模式修改锁。模式稳定锁确保锁定的资源不能被删除,模式修改锁确保其他会话不能参考正在修改的资源。
有些锁之间是兼容的,例如共享锁和修改锁之间。有些锁之间是不兼容的,例如排它锁和共享锁之间。下面的表1列出了SQL Server系统提供的各种锁之间的兼容性。
表1 SQL Server系统提供的各种锁之间的兼容性
锁名称 |
IS |
S |
U |
IX |
SIX |
X |
意图共享锁(IS) |
兼容 |
兼容 |
兼容 |
兼容 |
兼容 |
不兼容 |
共享锁(S) |
兼容 |
兼容 |
兼容 |
不兼容 |
不兼容 |
不兼容 |
修改锁(U) |
兼容 |
兼容 |
不兼容 |
不兼容 |
不兼容 |
不兼容 |
意图排它锁(IX) |
兼容 |
不兼容 |
不兼容 |
兼容 |
不兼容 |
不兼容 |
意图排它的共享锁(SIX) |
兼容 |
不兼容 |
不兼容 |
不兼容 |
不兼容 |
不兼容 |
排它锁(X) |
不兼容 |
不兼容 |
不兼容 |
不兼容 |
不兼容 |
不兼容 |
另外,除了表1中列出的锁之间的兼容性之外,对于模式锁来说,模式修改锁对于全部锁都是不兼容的,而模式稳定锁对于除了模式修改锁之外的全部锁都是兼容的。