首页 > 数据库 >SQLSERVER四种事务隔离级别

SQLSERVER四种事务隔离级别

时间:2023-05-07 20:35:16浏览次数:46  
标签:隔离 dbo READ TRAN SQLSERVER post id 四种

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

 

一:背景

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 UNCOMMITTED 和 nolock 的效果是一样的,会引发脏读现象,主要是因为 READ UNCOMMITTED 根本就不会对表记录使用任何锁,参考sql如下:


SET TRAN ISOLATION LEVEL READ UNCOMMITTED
GO

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

SQL 复制 全屏

接下来观察 sqlprofile 的输出。

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

三:总结

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

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

标签:隔离,dbo,READ,TRAN,SQLSERVER,post,id,四种
From: https://www.cnblogs.com/Leo_wl/p/17380067.html

相关文章

  • MySQL为什么使用可重复读隔离级别?
    历史原因:早期MySQL的binlog日志只有statement格式,在读已提交的隔离界别下,binlog日志存在bug,会导致主从复制不一致的情况。因此默认的隔离级别使用可重复读。bug:binlog日志中记录的语句顺序和原有顺序会不一致。先删除后插入的操作,同步导从库就变成了先插入后删除的操作了。MySQL......
  • 《安富莱嵌入式周报》第311期:300V可调节全隔离USB PD电源,开源交流负载分析仪,CANFD Tra
    周报汇总地址:http://www.armbbs.cn/forum.php?mod=forumdisplay&fid=12&filter=typeid&typeid=104 视频版:https://www.bilibili.com/video/BV1Hh4y1H7dR1、运行速度1Hz木头材料晶体管https://liu.se/en/news-item/varldens-forsta-tratransistor研究人员设计并测试了第......
  • SqlServer常用函数及时间处理
    YEAR() 函数返回一个整数值,它表示指定日期的年份,一般使用为:Year(时间),如:YEAR('2023-03-1416:50:08.543')--2023REPLACE() 字符串替换函数,一般使用为:REPLACE(被搜索字符串,需要替换的字符串,替换值),如:REPLACE('测试添加的','的','测试数据')--测试添加测试数据STUFF() ......
  • sqlserver 查询表中的主键、外键列及外键表,外表中的主键列
    --获取主键信息EXECsp_pkeys@table_name='tablename'--获取外键方法一SELECTField=(SELECTnameFROMsyscolumnsWHEREcolid=b.fkeyANDid=b.fkeyid),FKTable=object_name(b.rkeyid),FKKeyField=(SELECTnameFROMsyscolumnsWHEREcolid=b.rkeyANDid=b.rkeyid......
  • SQLServer配置远程访问连接
    打开对象资源管理器,右击,选择属性然后,开启-->程序-->MicrosoftSQLServer2019-->配置管理器:要确保CP/IP协议被开启了。右击,选中属性。点击IP地址标签,找到IPAII部分。如果TCP动态端口这一栏的值为0,表示sqlserver数据库引擎是监听在动态端口上,删除0,并将TCP动态端口设置成空白、......
  • 一路输入四路输出四进四出模拟信号隔离分配器0-5V/0-10V/1-5V转0-10mA/0-20mA/4-20mA
    主要特性:>>精度等级:0.1级、0.2级。产品出厂前已检验校正,用户可以直接使用>>辅助电源:5V/12V/15V/24VDC(范围±10%)>>国际标准一路信号输入:0-5V/0-10V/1-5V,0-10mA/0-20mA/4-20mA等>>四路输出标准信号:0-5V/0-10V/1-5V,0-10mA/0-20mA/4-20mA等,具有高负载能力>>全量程范围内极高的线性......
  • sqlserver查看表占硬盘大小
    --定义表变量--定义表变量DECLARE@TTABLE([Name]VARCHAR(max),[Rows]INT,[Reserved]VARCHAR(max),[Data_size]VARCHAR(max),[Index_size]VARCHAR(max),[Unused_size]VARCHAR(max))--将表占用情况存放到表变量INSERTINTO@TEXECsp_MSforeachtable"execsp_spaceused'?......
  • C#开发 B/S架构的实验室管理系统 云LIS系统(MVC + SQLserver + Redis)
    一、云LIS系统是将各种样本、免疫、临检、放免、及实验用的分析仪器,通过网络管理和传输实验分析过程中全部数据。对每一专业,实现检验申请、样本采集、样本核收、联机检验、质量控制、报告审核到报告发布的全环节的信息化管理平台。二、基于B/S架构的云LIS系统,整个系统的运行基于WE......
  • mysql 事务的隔离级别、MVCC
    《凤凰架构》一书中对事务的隔离级别以及事务的定义很清晰https://www.cnblogs.com/suBlog/p/16592859.html总结写锁:排他锁,其他事务不能写入数据,也不能施加读锁(可读,但是不可加读锁)读锁:共享锁,多个事务可以同时施加读锁,但是其他事务不能写入数据范围锁:不能修改范围内已有的数据......
  • Java线程池中的四种拒绝策略
    CallerRunsPolicy:这是默认的拒绝策略,当线程池队列已满并且无法处理新任务时,将由提交任务的线程来执行该任务。这种策略可以降低新任务的流量,但也会增加提交任务的线程的负载。AbortPolicy:当线程池队列已满并且无法处理新任务时,将抛出RejectedExecutionException异常,阻止新任......