SQL Server通过锁定资源来保证数据库的一致性。SQL Server中的锁不会对行、页、表或索引等资源有实际影响,它更像一个预订系统,所有任务在数据库内预订某些资源时都遵守它。过多的锁或长时间持有的锁会导致阻塞和其他问题,但锁本身也可能产生一些问题。
1 解决锁内存问题
为了确定SQL Server中锁使用的内存量,可以监视SQL Server中的“锁内存(KB)”计数器和系统监视器(Perfmon)中的“内存管理”对象。通过设置sp_configure中的锁选项,可以修改SQL Server中锁的内存配额。使用SQLServer:Locks计数器,可以了解更多关于锁行为的细节。
如果系统中的锁内存消耗完了,SQL Server不能分配更多的锁内存,session会收到消息1204:
The instance of the SQL Server Database Engine cannot obtain a LOCK resource at this time. Rerun your statement when there are fewer active users. Ask the database administrator to check the lock and memory configuration for this instance, or to check for long-running transactions. |
这个消息说得很清楚:需要增加锁的内存配额,或者减少系统中锁的数量。
如果锁占用很大的内存,应该首先尝试找出造成这么多锁的根本原因。例如,可能是SQL Server的锁升级不充分。如果是这样,就需要修改锁的配置。一旦修改了锁动态配置,就影响了锁升级的行为,由此可能造成意外的影响。
如果数据库不需要任何写访问,建议将其设置为只读的。这会降低系统中产生的锁的数量。在一个只读的数据库中,SQL Server仍会发行数据库的共享锁和读表的意向共享锁,但行锁、页锁及SERIALIZABLE隔离级别的行锁,都不会被发行。例如,对于只在夜间更新的报表数据库,用户可以将在白天对数据库的查询设置为只读的。这样做对锁内存的影响会降低,这也是SQL Server的锁管理器必须做的。还可以在同一台服务器上对只读数据库创建数据库快照,SQL Server不会在数据库快照上发行共享锁。
为了减少锁内存,同样建议将读操作与写操作分开。一种方法是把报表从一个OLTP系统中分开,通过创建报表服务器和使用事务复制或SQL Server集成服务(SSIS)来为另一个用户查询读操作的服务器获取数据。这会去掉OLTP主服务器的共享锁。如果数据库服务器能够支持这种方法,可以考虑用一个数据库快照来定期卸载读操作。在本章后面我们还可以看到使用一种基于行版本的快照隔离级别来减少读数据查询产生的锁。
2 锁超时
默认状态下,一个被阻塞的查询会无限地等待一个未被满足的锁的请求。通过使用LOCK_TIMEOUT设置,可以指定一个session锁等待的时间。当锁超时发生时,session会收到消息1222:
Lock request time out period exceeded.
使用LOCK_TIMEOUT给事务带来了问题,因为错误1222发生后,SQL Server只是退出当前程序语句,而并没有中止事务。因此需要在Transact-SQL代码中使用TRY/CATCH模块来捕获1222错误。若发生了超时,可能需要回滚事务。若要了解更多内容,可以参考Inside SQL Server 2005:The Storage Engine(《Microsoft SQL Server 2005:存储引擎》,电子工业出版社,2007)第8章的“设置锁超时”。