事务是关系型数据库的一个基础概念。它是作为单个逻辑工作单元执行的一系列操作。一个逻辑工作单元必须有4个属性,称为原子性、一致性、隔离性和持久性属性(ACID),只有这样才能成为一个事务。
原子性
事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。比如一个事务要修改100条记录,要不就100条都修改,要不就都不修改。不能发生只修改了其中50条,而另外50条没有改的情况。
一致性
事务在完成时,必须使所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。事务结束时,所有的内部数据结构(如B树索引或双向链表)都必须是正确的。
隔离性
由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务识别数据所处的状态,要么是另一并发事务修改它之前的状态,要么是第二个事务修改它之后的状态,事务不会识别中间状态的数据。也就是说,虽然用户是在并发操作,但是事务是串行执行的。对同一个数据对象的操作,事务读写修改是有先后顺序的。不是同一时间什么事情都能同时做的。
持久性
事务完成之后,它对于系统的影响是永久性的。哪怕SQLServer发生了异常终止,机器掉电,只要数据库文件还是完好的,事务做的修改必须还全部存在。
以上事务的定义对所有的关系型数据库都成立,不管是SQLServer,还是DB2、Oracle,都要遵从这些限制。但是,不同的数据库系统在事务的实现机制上有所不同,所以产生的效果在细节上是有差异的。尤其是SQLServer和Oracle,在事务的实现细节上有很大不同。两者在不同的应用场景上各有优劣,不好讲谁做得更好,谁做得差些。本章下面介绍的,是SQLServer实现事务的方法。
要实现业务逻辑上的ACID,有两方面任务。
1. 数据库程序员要负责启动和结束事务,确定一个事务的范围。
程序员定义数据修改的顺序,将组织的业务规则用T-SQL语句表现出来,然后将这些语句包括到一个事务中。换句话说,数据库程序员负责在必要并且合适的时间开启一个事务,将要做的操作以正确的顺序提交给SQLServer,然后在合适的时间结束这个事务。
2. SQL Server数据库引擎强制该事务的物理完整性。
数据库引擎应该有能力提供一种机制,保证每个逻辑事务的物理完整性。SQLServer是通过下面的方法做到的:
· 锁定资源,使事务保持隔离。
SQL Server通过在访问不同资源时需要申请不同类型锁的方式,实现了不同事务之间的隔离。如果两个事务会相互影响,那么在其中一个事务申请到了锁以后,另外一个事务就必须等待,直到前一个事务做完为止。
· 先写入日志方式,保证事务的持久性。
SQL Server通过先写入日志的方式,保证所有提交了的事务在硬盘上的日志文件里都有记录。即使服务器硬件、操作系统或数据库引擎实例自身出现故障,该实例也可以在重新启动时使用事务日志,将所有未完成的事务自动地回滚到系统出现故障的点,使得数据库进入一个从事务逻辑上来讲一致的状态。
· 事务管理特性,强制保证事务的原子性和一致性。
事务启动之后,就必须成功完成,否则数据库引擎实例将撤消该事务启动之后对数据所做的所有修改。
如果一个连接没有提交事务,SQLServer会保持这个事务一直在活动状态,并且不在意这个事务的长短或这个连接是否还在活动,直到这个连接自己提交事务,或登出(logout)SQL Server。如果在登出的时候还有未提交的事务,SQLServer会把这个事务范围内所做的所有操作撤销(回滚)。
所以,锁是SQL Server实现事务隔离的一部分,阻塞正是事务隔离的体现。要实现事务的隔离,阻塞不是SQLServer自找的,而是事务对SQL Server提出的要求,也是用户使用事务所要付出的代价。一个数据库开发者和管理员的工作,不是去消除阻塞,而是要把阻塞的时间和范围控制在一个合理的范围之内,使最终用户既能享受事务的ACID,又能享受预期的性能。完全消除阻塞,是不可能的事情。
换句话说,阻塞是实现事务的隔离所带来的不可避免的代价。为了达到良好的性能,数据库开发者和管理员要把阻塞的时间和范围控制在一个合理的范围之内。这不是一件很简单的工作,所以阻塞也将会是SQLServer的永恒的话题之一。做同样的事情,怎么做才能比较不容易产生大范围的阻塞呢?通常要从下面这几个方面进行考虑。
1.申请资源的互斥度。
如果不同的连接申请的锁都是相互兼容的,那么它们就不会产生阻塞。
2.锁的范围和数目多少。
做同样一件事情,SQLServer申请的锁的粒度和数目可能会不一样。一个良好设计的程序,可以使申请的锁的粒度和数目控制在最小范围之内。这样,阻塞住别人的可能性就能够大大降低。
3.事务持有锁资源的时间长短。
如果一个锁是大家都需要用的,那么每个人持有它的时间越短,阻塞对性能的影响就会越小。最好是申请得越晚越好,释放得越早越好。
为了达到以上的3个目的,需要研究一下SQL Server的锁资源模式和兼容性,以及它们是怎么被申请和释放的。