前言
事务一直以来是我最薄弱的环节,也是我打算重新学习SQL Server的出发点,关于SQL Server中事务将分为几节来进行阐述,Always to review the basics。
事务简介
事务是一个工作单元,可能包含查询和修改数据以及修改数据定义等多个活动。我们可以显式或隐式的定义事务边界。可以使用BEGIN TRAN或者BEGIN TRANSACTION语句显式的定义事务的开始。如果希望提交事务,可以使用COMMIT TRAN语句显式的定义事务结束。如果不希望提交事务(即要撤销更改),可以使用ROLLBACK TRAN或者ROLLBACK TRANSACTION语句-摘抄自SQL Server 2012基础教程。
如果不显式的标记事务的边界,默认情况下,SQL Server将把每个单独语句作为一个事务,换句话说,默认情况下,每个单独语句结束后SQL Server自动提交事务。可以通过一个叫做IMPLICIT_TRANSACTIONS的回话选项修改SQL Server处理隐式事务的方式,此选项默认为OFF。当此选项为ON时,不需要指定BEGIN TRAN语句标记事务的开始,但是必须以COMMIT TRAN或者ROLLBACK TRAN语句标记事务的结束-摘抄自SQL Server 2012基础教程。
事务具有原子性、一致性、隔离性、持续性四个属性,缩写字母为ACID。
(1)原子性:事务是一个工作单元,事务中的所有修改要么提交、要么撤销,在事务完成之前如果系统出现故障,重新启动时SQL Server会撤销所做的修改。
(2)一致性:一致性是指数据的状态,RDMS提供了以并发事务修改和查询数据的能力。
(3)隔离性:隔离是用于控制访问数据的机制,确保事务所访问数据是在其期望的一致性级别中的数据,SQL Server支持两种不同的模式来处理隔离:基于锁的传统模式和基于行版本控制的新模式,在企业内部部署的SQL Server中,默认是基于锁的模式。
(4)持续性:数据修改写入到数据库磁盘上的数据部分之前,总是先写入到数据库的事务日志磁盘,在提交之后,指令记录在事务日志的磁盘上,在尚未修改磁盘上的数据部分之前,事务被认为是持续的,在系统正常或是出现故障启动时,SQL Server将检查每个数据库的事务日志并执行具有两个阶段的恢复过程-重做和撤销。可以用如下图表示四个事务属性。
图片来源:https://blog.sqlauthority.com/2007/12/09/sql-server-acid-atomicity-consistency-isolation-durability/
说到事务就联想到并发,为了解决事务中的并发我们则不得不讨论下锁,所以接下来我们首先熟悉一下锁的模式-排他锁和共享锁。
排他锁:当试图修改数据时,事务会请求数据资源的一个排他锁,而不管其隔离级别,如果授予了锁,那么排他锁知道事务结束才会被解除,对于单语句事务意味着直到语句完成锁定才会被解除,对于多语句事务意味着直到完成所有语句并通过COMMIT TRAN或ROLLBACK TRAN命令结束才会解除锁定。排他锁之所以被称为排他,是因为如果一个事务正在修改行,直到事务完成,其他事务都不能修改相同的行,这是默认的修改行为。然而,另外一个事物能不能读取相同的行,取决于它的隔离级别。
共享锁:当试图读取数据时,事务默认请求数据资源的一个共享锁,并且一旦语句完成资源读取,会立即释放资源的共享锁。共享锁之所以被称为共享,是因为多个事务可以同时持有相同资源的共享锁。虽然在修改数据时,不能修改锁的模式和所需的持续时间,但是通过改变其隔离级别,可以在读取数据时控制锁定的处理方式。
讲述了锁的两种重要的模式,那么问题来了,锁的存在会导致什么问题?请继续往下看。我们试图去更新一条数据,此时并未进行提交
BEGIN TRAN UPDATE Production.Products SET unitprice += 1.00 WHERE productid = 2
接下来我们再来读取该条记录的数据。
SELECT productid, unitprice FROM Production.Products WHERE productid = 2
接下来我们进行查询,此时会发现一直在查询中直到达到设置的查询超时时间为止。
当更新行时会获取该资源上的排他锁,如果更新成功,SQL Server会将锁授予会话,所以直到事务完成,其排他锁会一直存在,当读取数据时需要获取该资源上的共享锁,但是更新行会话一直存在即以排他锁锁定,但是排他锁和共享锁不能兼容,此时会导致查询阻塞不得不进行等待。说明锁在并