SQLSERVER 的四个事务隔离级别到底怎么理解?

sqlserver,四个,事务,隔离,级别,到底,怎么,理解 · 浏览次数 : 1594

小编点评

**背景** SQL Server 中的四种事务隔离级别:READ UNCOMMITTEDREAD COMMITTEDSERIALIZABLEREPEATABLE READ。 **四种隔离级别** 1. **READ UNCOMMITTED**:读不提交的读取操作,不加锁,可能引发脏读。 2. **SERIALIZABLE**:串行化事务,在事务提交之前对表进行锁,确保数据完整性。 3. **REPEATABLE READ**:重复读,在事务提交之前对表进行锁,保证读取到的数据一致性。 4. **READ COMMITTED**:只对表和索引结构进行锁,读取操作不受锁,提高效率。 **锁过程观察** SQL Server 使用 S 锁和 X 锁来实现事务隔离级别。 * **S 锁**:锁定表记录的 S 类锁,其他事务只能等待该 S 锁释放才能获取锁。 * **X 锁**:锁定表记录的 X 类锁,其他事务只能等待该 X 锁释放才能获取锁。 **SQL Server 的 SNAPSHOT 隔离级别** SQL Server 支持带版本的 SNAPSHOT 隔离级别,它会在读操作期间创建一个表副本,并使用 S 锁来保证数据的完整性。在真实场景中,SNAPSHOT 隔离级别通常会给 TempDB 造成很大的压力,因此通常不会启用。 **总结** * READ UNCOMMITTED:不加锁,容易引发脏读。 * SERIALIZABLE:串行化事务,在提交之前对表进行锁。 * REPEATABLE READ:重复读,在提交之前对表进行锁。 * READ COMMITTED:只对表和索引结构进行锁,读取操作不受锁。 * SNAPSHOT:在读操作期间创建一个表副本,并使用 S 锁保证数据的完整性。

正文

一:背景

1. 讲故事

在有关SQLSERVER的各种参考资料中,经常会看到如下四种事务隔离级别。

  • READ UNCOMMITTED
  • READ COMMITTED
  • SERIALIZABLE
  • REPEATABLE READ

随之而来的是大量的文字解释,还会附带各种 脏读, 幻读, 不可重复读 常常会把初学者弄得晕头转向,其实事务的本质就是隔离,落地就需要锁机制,理解这四种隔离方式的花式加锁,应该就可以入门了,那如何可视化的观察 过程呢?这里借助 SQL Profile 工具。

二:四种事务隔离方式

1. 测试数据准备

还是用上一篇创建的 post 表,脚本如下:


CREATE TABLE post(id INT IDENTITY,content char(4000))
GO

INSERT INTO dbo.post VALUES('aaa')
INSERT INTO dbo.post VALUES('bbb')
INSERT INTO dbo.post VALUES('ccc');
INSERT INTO dbo.post VALUES('ddd');
INSERT INTO dbo.post VALUES('eee');
INSERT INTO dbo.post VALUES('fff');

有了测试数据之后,我们按照隔离级别 高 -> 低 的顺序来观察吧。

2. SERIALIZABLE 事务

事务串行化 其实很好理解,如果要在 C# 中找对应那就是 ReaderWriterLock,读写事务是完全排斥的,接下来把 SQLSERVER 的隔离级别调整为 SERIALIZABLE


SET TRAN ISOLATION LEVEL SERIALIZABLE
GO

BEGIN TRAN 
SELECT * FROM dbo.post WHERE id=3
COMMIT

打开 profile,选择 lock:Acquired, lock:Released,SQL:StmtStarting 选项,开启观察。

从图中可以清楚的看到,SQLSERVER 直接对 post 附加了 S 锁,在 COMMIT 之后才真正的释放,在 S 锁期间, Insert 和 Update 引发的 X 锁是进不来的,所以就会存在相互阻塞的情况,也许这就是串行化的由来吧。

sqlserver 是一个支持多用户并发的数据库程序,如果锁粒度这么粗,必定给并发带来非常大的负面影响,不过文章开头的那三个指标 脏读, 幻读, 不可重复读 肯定都是不会出现的。

2. REPEATABLE READ 事务

什么叫 可重复读 呢?简而言之就是同一个 select 查询执行二次,不会出现记录修改的情况,在真实场景中两次 select 查询期间,可能会有其他事务修改了记录,如果当前是 REPEATABLE READ 模式,这是被禁止的,接下来的问题是如何落地实现呢?我们来看看 SQLSERVER 是如何做到的,参考sql 如下:


SET TRAN ISOLATION LEVEL REPEATABLE READ
GO

BEGIN TRAN 
SELECT * FROM dbo.post WHERE id=3
COMMIT

这个图可能有些朋友看不懂,我稍微解释一下吧,数据库由数据页Page组成,数据页由记录RID 组成,有了这个基础就好理解了, SQLSERVER 会在事务期间把 1:489:0 也就是 id=3 这个记录全程附加 S 锁,直到事务提交才释放 S 锁,在事务期间任何对它修改的 X 锁都无法对其变更,从而实现事务期间的 可重复读 功能,如果大家不明白可以再琢磨琢磨。

这里有一个细节需要大家注意一下,可重复读 的场景下会出现 幻读 的情况,幻读就是两次查询出的结果集可能会不一样,比如第一次是 3 条记录,第二次变成了 5 条记录,为了方便理解我来简单演示一下。

  • 会话1

SET TRAN ISOLATION LEVEL REPEATABLE READ
GO

BEGIN TRAN 
SELECT * FROM dbo.post WHERE id >3
WAITFOR DELAY '00:00:05'
SELECT * FROM dbo.post WHERE id >3
COMMIT

  • 会话2

会话1 执行的 5s 期间执行 会话2 语句。


BEGIN TRAN 
INSERT INTO dbo.post(content) VALUES ('gggggg')
COMMIT

稍等片刻之后,会发现多了一个 记录7 ,截图如下:

3. READ COMMITTED

提交读 是目前 SQLSERVER 默认的隔离级别,它是以不会出现 脏读 为唯一目标,何为脏读,简而言之就是读取到了别的事务未提交的修改数据,这个数据有可能会被其他事务在后续回滚掉,如果真的被其他事务 回滚 了,那你读到了这样的数据就是 错误 的数据,可能会给你的系统带来非常隐蔽的 bug,为了说明这个现象,我们用两个会话来测试一下帮助大家理解。

  • 会话1

在这个会话中,将 id=3 的记录修改成 zzzzz


BEGIN TRAN 
UPDATE dbo.post SET content='zzzzz' WHERE id=3
WAITFOR DELAY '00:00:05'
ROLLBACK

  • 会话2

这个会话中,重复执行sql查询。


BEGIN TRAN 
SELECT * FROM dbo.post WITH(NOLOCK) WHERE id =3   -- 脏读啦
WAITFOR DELAY '00:00:05'
SELECT * FROM dbo.post WITH(NOLOCK) WHERE id =3   -- 正确的数据
COMMIT

为了实现脏读这里加了 nolock 关键词,从图中明显的看到,获取的 zzzzz 数据是错误的,在一些和钱打交道的系统中是被严厉禁止的。

有了这些基础再理解 可提交读 可能会容易些,是不是很好奇 SQLSERVER 是如何实现的呢? 参考 sql 如下:


SET TRAN ISOLATION LEVEL READ COMMITTED
GO

BEGIN TRAN 
SELECT * FROM dbo.post  WHERE id =3  
COMMIT

从加锁流程看,SQLSERVER 会逐一扫描数据页附加 IS 锁,扫完马上就释放,不像前面那样保持到 COMMIT 之后,如果找到记录所在的 Page 时,会对下面的所有记录附加 S 锁,这个时候 X 锁就进不来了,这就是它的实现原理,大家可以把刚才的 脏读 的sql中的 nolock 去掉试试看,两次读取结果都是一样的。

4. READ UNCOMMITTED

本质上来说 READ UNCOMMITTEDnolock 的效果是一样的,会引发脏读现象,主要是因为 READ UNCOMMITTED 根本就不会对表记录使用任何锁,参考sql如下:


SET TRAN ISOLATION LEVEL READ UNCOMMITTED
GO

BEGIN TRAN 
SELECT * FROM dbo.post  WHERE id =3  
COMMIT

接下来观察 sqlprofile 的输出。

可以看到 READ UNCOMMITTED 只会对堆表结构这种架构附加锁,不会对表中记录附加任何锁,也就会引发 脏读 现象。

三:总结

其实 SQLSERVER 还有带版本的 SNAPSHOT 隔离级别,在真实场景中往往会给 TempDB 造成很大的压力,这里就不介绍了。

相信通过 Profile 观察到的加锁动态过程,会让大家有更深入的理解。

与SQLSERVER 的四个事务隔离级别到底怎么理解?相似的内容:

SQLSERVER 的四个事务隔离级别到底怎么理解?

一:背景 1. 讲故事 在有关SQLSERVER的各种参考资料中,经常会看到如下四种事务隔离级别。 READ UNCOMMITTED READ COMMITTED SERIALIZABLE REPEATABLE READ 随之而来的是大量的文字解释,还会附带各种 脏读, 幻读, 不可重复读 常常会把

SQLSERVER 快照隔离级别 到底怎么理解?

一:背景 1. 讲故事 上一篇写完 SQLSERVER 的四个事务隔离级别到底怎么理解? 之后,有朋友留言问什么时候可以把 snapshot 隔离级别给补上,这篇就来安排,快照隔离级别看起来很魔法,不过在修车之前,得先看下怎么开车。 二:snapshot 隔离详解 1. snapshot 之前的困境

SQLSERVER 的主键索引真的是物理有序吗?

一:背景 1. 讲故事 最近在看 SQL SERVER 2008 查询性能优化,书中说当一个表创建了聚集索引,那么表中的行会按照主键索引的顺序物理排列,这里有一个关键词叫:物理排列,如果不了解底层原理,真的会被忽悠过去,其实仔细想一想不可能实现严格的 物理排列 ,那对性能是非常大的损害,本篇我们就从

SQLSERVER 的复合索引和包含索引到底有啥区别?

一:背景 1. 讲故事 在 SQLSERVER 中有非常多的索引,比如:聚集索引,非聚集索引,唯一索引,复合索引,Include索引,交叉索引,连接索引,奇葩索引等等,当索引多了之后很容易傻傻的分不清,比如:复合索引 和 Include索引,但又在真实场景中用的特别多,本篇我们就从底层数据页层面厘清

SQLSERVER 的 nolock 到底是怎样的无锁?

一:背景 1. 讲故事 相信绝大部分用 SQLSERVER 作为底层存储的程序员都知道 nolock 关键词,即使当时不知道也会在踩过若干阻塞坑之后果断的加上 nolock,但这玩意有什么注意事项呢?这就需要了解它的底层原理了。 二:nolock 的原理 1. sql 阻塞还原 为了方便讲述,先创建

SQLSERVER 的 truncate 和 delete 有区别吗?

一:背景 1. 讲故事 在面试中我相信有很多朋友会被问到 truncate 和 delete 有什么区别 ,这是一个很有意思的话题,本篇我就试着来回答一下,如果下次大家遇到这类问题,我的答案应该可以帮你成功度过吧。 二:区别详解 1. 思考 从宏观角度来说, delete 是 DML 语句, tru

聊一聊 SQLSERVER 的行不能跨页

一:背景 1. 讲故事 相信有很多朋友在学习 SQLSERVER 的时候都听说过这句话,但大多都是记忆为主,最近在研究 SQLSERVER,所以我们从 底层存储 的角度来深入理解下。 二:理解数据页 1. 数据页的组织 在前面的文章中我也说过,一个 数据页 是 8k 大小,那这 8k 是如何组织的呢

SQLSERVER 语句交错引发的死锁研究

一:背景 1. 讲故事 相信大家在使用 SQLSERVER 的过程中经常会遇到 阻塞 和 死锁,尤其是 死锁,比如下面的输出: (1 row affected) Msg 1205, Level 13, State 51, Line 5 Transaction (Process ID 62) was

[转帖]关于SQLSERVER的max degree of parallelism参数

http://www.gaodaima.com/228823.html max degree of parallelism说明:本文来源gao($daima.com搞@代@#码8网^http://msdn.microsoft.com/zh-cn/library/ms181007.aspx 当 SQL

【转帖】sqlserver 在高并发的select,update,insert的时候出现死锁的解决办法

最近在使用过程中使用SqlServer的时候发现在高并发情况下,频繁更新和频繁查询引发死锁。通常我们知道如果两个事务同时对一个表进行插入或修改数据,会发生在请求对表的X锁时,已经被对方持有了。由于得不到锁,后面的Commit无法执行,这样双方开始死锁。但是select语句和update语句同时执行,