SQL Server 事务隔离级别的解析

近来在项目中遇到的一些有关事务的问题,跟同事间讨论了一下,后面翻看了一些书籍和做了一些测试,趁有点时间把它写下来,一来加深印象,二来希望对大家有所帮助,当然,由于自身水平问题,如理解有误,还请大牛指出, 本人在此先行谢过.

事情首先是这样引起的, 同事写的一个导入,但在导入的过程中,由于要插多条数据,当有些数据未能插入时,却没有回滚所有的数据,而是往下执行,这样问题就来了,无法得知系统到底导了多少条记录.用户需要的结果是,要么都导入成功,要么都不成功.

if OBJECT_ID(‘tb1‘) is not null
drop table tb1
go
create table tb1(id int identity primary key ,name varchar(10))  

 -- set xact_abort on
 begin transaction test
 insert into tb1 values(‘getonjew‘);
 insert into tb1 values(‘caozx‘);
 insert into tb1 values(‘kevin-xxxxxxxx‘);
 insert into tb1 values(‘toby‘);
 insert into tb1 values(‘idawong‘);
 commit transaction test

 go
 select * from tb

以上代码得出信息与结果

信息

(1 row(s) affected)

(1 row(s) affected)

Msg 8152, Level 16, State 14, Line 7 String or binary data would be truncated. The statement has been terminated.

(1 row(s) affected)

(1 row(s) affected)

(4 row(s) affected)

结果(这里大家要注意下,即使第三条数据未成功,但其ID已被占用)

1 getonjew
2 caozx
4 toby
5 idawong

这里就是与项目中遇到的情况类似了,但这不是我们想要的结果,如果想达到想要的结果,只需要把上面的注释行.set xact_abort on  打开即可了. 但这不是这里所要讲述的. 有关事务的问题,我重新的梳理了一下. 现整理如下.请大家批评指证.

SQL事务级别用于控制并发用户如何读写数据的操作,同时对性能也有一定的影响作用。大家应根据实际的使用情况使用不同的级别.

事务隔离级别主要通过影响读操作来间接地影响写操作;可以在会话级别上设置事务隔离的级别,也可以在表上设置事务隔离级别。
事务隔离级别总共有6个级别:
READ UNCOMMITTED(未提交读,读脏),相当于(NOLOCK)
READ COMMITTED(已提交读,默认级别)
REPEATABLE READ(可以重复读),相当于(HOLDLOCK)
SERIALIZABLE(可序列化)
SNAPSHOT(快照)
READ COMMITTED SNAPSHOT(已经提交读隔离)
对于前四个隔离级别:READ UNCOMMITTED<READ COMMITTED<REPEATABLE READ<SERIALIZABLE
隔离级别越高,读操作的请求锁定就越严格,锁的持有时间久越长;所以隔离级别越高,一致性就越高,并发性就越低,同时性能也相对影响越大.

首先,可以在命令窗口输入  DBCC USEROPTIONS 查看目前隔离级别,这里同时能查看到一些其它的属性.

下面我们来说一下如何设置会话隔离级别

SET TRANSACTION ISOLATION LEVEL <ISOLATION NAME>
--设置查询表隔离
SELECT ....FROM <TABLE> WITH (<ISOLATION NAME>) 

1.READ UNCOMMITTED

READ UNCOMMITTED:未提交读,可能读到脏数据

READ UNCOMMITTED:读操作不申请锁,运行读取未提交的修改,也就是允许读脏数据,读操作不会影响写操作请求排他锁.

下面举例说明:

建立一个Courses表, 里面包含以下数据

新建一会话(即新开一查询窗口)运行以下命令

BEGIN TRANSACTION
UPDATE Courses
SET SCORE=SCORE+7
WHERE ID=1

SELECT ID,SCORE FROM Courses
WHERE ID=1

得到以下结果

新建另一个会话,执行

/*先不添加隔离级别,默认是READ COMMITTED,由于数据之前的更新操作使用了排他锁(事务没有提交), 查询一直在等待锁释放*/
SELECT ID,SCORE FROM Courses
WHERE ID=1 

如果将隔离级别设置为

---将查询的隔离级别设置为READ UNCOMMITTED允许未提交读,读操作之前不请求共享锁。
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT ID,SCORE FROM Courses
WHERE ID=1
--当然也可以使用表隔离,效果是一样的
SELECT ID,SCORE FROM Courses  WITH (NOLOCK)
WHERE ID=1 

得出结果

假设在会话1中对操作执行回滚操作ROLLBACK TRANSACTION,这样分数还是之前的80但是会话2中则读取到的是回滚前的分数87,这样就属于一个读脏操作.

2.READ COMMITTED

READ COMMITTED(已提交读)是SQL SERVER默认的隔离级别,可以避免读取未提交的数据,隔离级别比READ UNCOMMITTED未提交读的级别高; 该隔离级别读操作之前首先申请并获得共享锁,允许其他读操作读取该锁定的数据,但是写操作必须等待锁释放,一般读操作读取完就会立刻释放共享锁。

首先,再提交之前会话1的代码

BEGIN TRANSACTION
UPDATE Courses
SET SCORE=SCORE+7
WHERE ID=1

SELECT ID,SCORE FROM Courses
WHERE ID=1

这个时候,在会话2中,读取数据时一直在等待. 直到会话1提交了事务之后

COMMIT TRANSACTION 

此时在会话2才能读到数据,但这个时候读到的数据结果是修改后的结果,所以读的不是脏数据.

但是由于READ COMMITTED读操作一完成就立即释放共享锁,读操作不会在一个事务过程中保持共享锁,也就是说在一个事务的的两个查询过程之间有另一个会话对数据资源进行了更改,会导致一个事务的两次查询得到的结果不一致,这种现象称之为不可重复读,这个时候我们就要引入更高一级的隔离级别了.

3.REPEATABLE READ

REPEATABLE READ(可重复读):保证在一个事务中的两个读操作之间,其他的事务不能修改当前事务读取的数据,该级别事务获取数据前必须先获得共享锁同时获得的共享锁不立即释放一直保持共享锁至事务完成,所以此隔离级别查询完并提交事务很重要。

首先,我们重置我们ID=1的数据为80,

在会话1中执行查询ID=1,将回话级别设置为REPEATABLE READ

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN TRANSACTION
SELECT ID,SCORE FROM Courses
WHERE ID=1 

新建会话2修改ID=1的分数.

---由于会话1的隔离级别REPEATABLE READ申请的共享锁一直要保持到事务结束,所以回话2无法获取排他锁,处于等待状态
UPDATE Courses
SET SCORE=SCORE+7
WHERE ID=1

在会话1中执行下面语句,然后提交事务

SELECT ID,SCORE FROM Courses
WHERE ID=1 

COMMIT TRANSACTION

得出以下结果,结果一致.

会话1的两次查询得到的结果一致,前面的两个隔离级别无法得到一致的数据,此时事务已提交同时释放共享锁,会话2申请排他锁成功,对行执行更新操作.

REPEATABLE READ隔离级别保证一个事务中的两次查询到的结果一致,同时保证了丢失更新,所谓的丢失更新是:两个事务同时读取了同一个值然后基于最初的值进行计算,接着再更新,就会导致两个事务的更新相互覆盖。 例如公司开会申请会议室,两个人同时预定同一会议室,首先两个人同时查询到还有一间房间可以预定,然后两个人同时提交预定操作,事务1执行num=1-1,同时事务2也执行num=1-1最后修改num=0,这就导致两个人其中一个人的操作被另一个人所覆盖,REPEATABLE READ隔离级别就能避免这种丢失更新的现象,当事务1查询房间时事务就一直保持共享锁直到事务提交,而不是像前面的几个隔离级别查询完就不共享锁,就能避免其他事务获取排他锁。(当然这里只是举做例子,在实际开发中未必需要设置这个级别,可以在提交的过程中再到数据库验证一下,如果符合条件就订会议室,否则给出提示,根据情况而定)

4.SERIALIZABLE

SERIALIZABLE(可序列化),对于前面的REPEATABLE READ能保证事务可重复读,但是事务只锁定查询第一次运行时获取的数据资源(数据行),而不能锁定查询结果之外的行,就是原本不存在于数据表中的数据。因此在一个事务中当第一个查询和第二个查询过程之间,有其他事务执行插入操作且插入数据满足第一次查询读取过滤的条件时,那么在第二次查询的结果中就会存在这些新插入的数据,使两次查询结果不一致,这种读操作称之为幻读。 为了避免幻读需要将隔离级别设置为SERIALIZABLE

-- 先测试一下,之前的可重复读不能保证幻读
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN TRANSACTION
SELECT ID,SCORE FROM Courses

得到以下结果

然后在会话2插入一条数据.

INSERT INTO Courses VALUES(6,99)

返回会话1,再查询并提交事务.

SELECT ID,SCORE FROM Courses
COMMIT TRANSACTION

得出结果

第二次查询到的数据包含了会话2新插入的数据,两次查询结果不一致(验证之前的隔离级别不能保证幻读)

如果在前面设置隔离级别为SERIALIZABLE的话,则两次的查询结果会一致, 也即会话2插入的数据,不会在第二次查询结果中得到.

实验完后,重置会话级别显默认级别

SET TRANSACTION ISOLATION LEVEL READ COMMITTED

5.SNAPSHOT

SNAPSHOT快照:SNAPSHOT和READ COMMITTED SNAPSHOT两种隔离(可以把事务已经提交的行的上一版本保存在TEMPDB数据库中) SNAPSHOT隔离级别在逻辑上与SERIALIZABLE类似, READ COMMITTED SNAPSHOT隔离级别在逻辑上与 READ COMMITTED类似 不过在快照隔离级别下读操作不需要申请获得共享锁,所以即便是数据已经存在排他锁也不影响读操作。而且仍然可以得到和SERIALIZABLE与READ COMMITTED隔离级别类似的一致性;如果目前版本与预期的版本不一致,读操作可以从TEMPDB中获取预期的版本。

如果启用任何一种基于快照的隔离级别,DELETE和UPDATE语句在做出修改前都会把行的当前版本复制到TEMPDB中,而INSERT语句不需要在TEMPDB中进行版本控制,因为此时还没有行的旧数据

无论启用哪种基于快照的隔离级别都会对更新和删除操作产生性能的负面影响,但是有利于提高读操作的性能因为读操作不需要获取共享锁;

5.1 SNAPSHOT

SNAPSHOT 在SNAPSHOT隔离级别下,当读取数据时可以保证操作读取的行是事务开始时可用的最后提交版本 同时SNAPSHOT隔离级别也满足前面的已提交读,可重复读,不幻读;该隔离级别实用的不是共享锁,而是行版本控制 使用SNAPSHOT隔离级别首先需要在数据库级别上设置相关选

在打开的所有查询窗口中执行以下操作

ALTER DATABASE TEST SET ALLOW_SNAPSHOT_ISOLATION ON;

然后

在会话1中打开事务,将ID=1的分数加7,并查询跟新后的分数
BEGIN TRANSACTION
UPDATE Courses
SET SCORE=SCORE+7
WHERE ID=1

SELECT  ID,SCORE FROM Courses
WHERE ID=1
---查询到更新后的分数87

---在会话2中将隔离级别设置为SNAPSHOT,并打开事务(此时查询也不会因为会话1的排他锁而等待,依然可以查询到数据)
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
BEGIN TRANSACTION
SELECT  ID,SCORE FROM Courses
WHERE ID=1

---查询到的结果还是会话1修改前的分数,由于会话1在默认的READ COMMITTED隔离级别下运行,SQL SERVER必须在更新前把行的一个副本复制到TEMPDB数据库中
--在SNAPSHOT级别启动事务会请求行版本

---现在在会话1中执行提交事务,此时ID=1的分数是87
COMMIT TRANSACTION

---再次在会话2中查询ID=1的分数并提交事务,结果还是80,因为事务要保证两次查询的结果相同

SELECT  ID,SCORE FROM Courses
WHERE ID=1

COMMIT TRANSACTION

---此时如果在回话2中重新打开一个事务,查询到的ID=1的分数是87
BEGIN TRANSACTION
SELECT  ID,SCORE FROM Courses
WHERE ID=1

COMMIT TRANSACTION

/*SNAPSHOT隔离级别保证操作读取的行是事务开始时可用的最后已提交版本,由于会话1的事务未提交,所以ID=1的最后提交版本还是修改前的分数80,所以会话2读取到的价格是会话2事务开始前的已提交版本分数80,当会话1提交事务后,会话2重新新建一个事务此时事务开启前的分数已经是87了,所以查询到的分数是87,同时SNAPSHOT隔离级别还能保证SERIALIZABLE的隔离级别*/

5.2 READ COMMITTED SNAPSHOT

READ COMMITTED SNAPSHOT也是基于行版本控制,但是READ COMMITTED SNAPSHOT的隔离级别是读操作之前的最后已提交版本,而不是事务前的已提交版本,有点类似前面的READ COMMITTED能保证已提交读,但是不能保证可重复读,不能避免幻读,但是又比 READ COMMITTED隔离级别多出了不需要获取共享锁就可以读取数据
要启用READ COMMITTED SNAPSHOT隔离级别同样需要修改数据库选项,在会话1,会话2中执行以下操作(执行下面的操作当前连接必须是数据库的唯一连接,可以通过查询已连接当前数据库的进程,然后KILL掉那些进程,然后再执行该操作,否则可能无法执行成功)

开始前重置 ID=1的分数为80

-----在会话1中打开事务,将ID=1的分数加7,并查询跟新后的分数,并保持事务一直处于打开状态
BEGIN TRANSACTION
UPDATE Courses
SET SCORE=SCORE+7
WHERE ID=1

--查询到的分数是87,
select ID,SCORE FROM Courses
WHERE ID=1

---在会话2中打开事务查询ID=1并一直保持事务处于打开状态(此时由于会话1还未提交事务,所以会话2中查询到的还是会话1执行事务之前保存的行版本)
BEGIN TRANSACTION
select ID,SCORE FROM Courses
WHERE ID=1
--查询到的分数还是80

---在会话1中提交事务
COMMIT TRANSACTION 

---在会话2中再次执行查询ID=1的分数,并提交事务
select ID,SCORE FROM Courses
WHERE ID=1
COMMIT TRANSACTION
--此时的分数为会话1修改后的分数87,而不是事务之前已提交版本的价格,也就是READ COMMITTED SNAPSHOT隔离级别在同一事务中两次查询的结果不一致.
时间: 2024-08-23 06:26:21

SQL Server 事务隔离级别的解析的相关文章

SQL Server 事务隔离级别详解

原文:SQL Server 事务隔离级别详解 标签: SQL SEERVER/MSSQL SERVER/SQL/事务隔离级别选项/设计数据库事务级别 SQL 事务隔离级别 概述 隔离级别用于决定如果控制并发用户如何读写数据的操作,同时对性能也有一定的影响作用. 步骤 事务隔离级别通过影响读操作来间接地影响写操作:可以在回话级别上设置事务隔离级别也可以在查询(表级别)级别上设置事务隔离级别.事务隔离级别总共有6个隔离级别:READ UNCOMMITTED(未提交读,读脏),相当于(NOLOCK)R

Sql Server 事务日志

Sql Server事务日志文件是数据库文件的重要组成部分,事务日志主要用来存放数据库的修改记录.数据库为了得到更高的写入效率和性能,同时保证ACID特性,数据在写入时,会将更新先写入事务日志,因为事务日志是连写的,所以写事务会比较快.简单来说,顺序写入时,磁盘的磁头会保持在一定的区域内连续写入,而数据写入数据文件时,有随机性,磁盘的磁头移动消耗的时间要比数据写入日志文件时多. Sql Server对于事务日志文件的管理,是将日志文件在逻辑上分成若干个文件(VLFS),方便管理. 创建一个1M的

如何处理SQL Server事务复制中的大事务操作

如何处理SQL Server事务复制中的大事务操作 事务复制的工作机制 事务复制是由 SQL Server 快照代理.日志读取器代理和分发代理实现的.快照代理准备快照文件(其中包含了已发布表和数据库对象的架构和数据),然后将这些文件存储在快照文件夹中,并在分发服务器中的分发数据库中记录同步作业. 日志读取器代理监视为事务复制配置的每个数据库的事务日志,并将标记为要复制的事务从事务日志复制到分发数据库中,分发数据库的作用相当于一个可靠的存储-转发队列. 分发代理将快照文件夹中的初始快照文件和分发数

SQL Server事务执行一半出错是否自动回滚整个事务 【转】

http://www.2cto.com/database/201308/234728.html SQL Server事务执行一半出错是否自动回滚整个事务 大家都知道SQL Server事务是单个的工作单元.如果某一事务成功,则在该事务中进行的所有数据修改均会提交,成为数据库中的永久组成部分.如果事务遇到错误且必须取消或回滚,则所有数据修改均被清除. 所以是不是说事务出错一定会回滚整个事物呢? 先看几个个例子: --createtable create table testrollback(idi

SQL Server 连接问题案例解析(1)

SQL Server 连接问题案例解析(1) 转载自:http://blogs.msdn.com/b/apgcdsd/archive/2015/04/27/sql.aspx?CommentPosted=true#commentmessage Microsoft Network Monitor(Netmon)是由微软发布的一款网络协议数据分析工具,利用Netmon可以捕获网络数据并进行查看和分析. 在处理SQL Server 的连接问题时,Netmon常常会起到关键的作用.在本篇博文中,我将为大家

SQL Server事务日志分析

SQL Server事务日志分析 fn_dblog()和fn_dump_dblog()函数介绍 SQL Server有两个未公开的函数fn_dblog()和fn_dump_dblog()非常有用并且提供的信息量很大.你可以使用这些函数来获取100多列大量的有用信息. fn_dblog()用于分析数据库当前的事务日志文件,它需要两个参数,分别为事务开始LSN和结束LSN,默认为NULL,表示返回事务日志文件的所有日志记录. 例如: SELECT * FROM fn_dblog(null,null)

SQL Server 事务嵌套

原文:SQL Server 事务嵌套 示例代码: DECLARE @TranCounter INT; SET @TranCounter = @@TRANCOUNT; IF @TranCounter > 0 -- Procedure called when there is -- an active transaction. -- Create a savepoint to be able -- to roll back only the work done -- in the procedure

理解Sql Server 事务隔离层级(Transaction Isolation Level)

关于Sql Server 事务隔离级别,百度百科是这样描述的 隔离级别:一个事务必须与由其他事务进行的资源或数据更改相隔离的程度.隔离级别从允许的并发副作用(例如,脏读或虚拟读取)的角度进行描述. 隔离级别共5种: read uncommitted | 0 未提交读read committed | 1 已提交读repeatable read | 2 可重复读serializable | 3 可序列化snapshot 快照(2005版本以后新加) 以下面的图为例,在事务A中会根据条件读取TABLE

SQL Server 事务复制分发到订阅同步慢

原文:SQL Server 事务复制分发到订阅同步慢 最近发现有一个发布经常出现问题,每几天就出错不同步,提示要求初始化.重新调整同步后,复制还是很慢!每天白天未分发的命令就达五六百万条!要解决慢的问题,需要了解从发布数据库到订阅数据库中,有哪些操作,才知道哪个步骤同步缓慢. 这是很久之前自己做的一张图,主要描述发布到分发.分发到订阅中,复制使用了哪些操作,如下图: 发布到分发: 在发布中,复制是使用日志读取器读(sp_replcmds)取发布数据库中的事务日志的,日志读取器是按事务顺序读取的,