并发事务下各数据库外部表现实测之一(SQL Server篇)

本文涉及的产品
云数据库 RDS SQL Server,基础系列 2核4GB
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
简介: 当同时有多个事务访问相同数据时,DBMS会采取锁或MVCC的机制确保数据的完整性。反映到应用程序上的表现可能就是等待或报错。不同DBMS因为采取的机制或策略不同,其外部表现也有很大差异。
当同时有多个事务访问相同数据时,DBMS会采取锁或MVCC的机制确保数据的完整性。反映到应用程序上的表现可能就是等待或报错。不同DBMS因为采取的机制或策略不同,其外部表现也有很大差异。于是笔者决定通过测试一探究竟。先以SQL Server作为对象进行测试。

一、 背景知识
对并发事务的数据完整性保护的强弱可通过隔离级别控制,如果过强会影响并发性能,过弱可能不满足应用的需要。SQL 标准用三个存在并发的事务时应该避免的现像定义了四个级别的事务隔离。 
脏读 
  一个事务读取了另一个未提交事务写入的数据。
不可重复读 
  一个事务重新读取前面读取过的数据,发现该数据已经被另一个已提交事务修改。
幻读 
  一个事务重新执行一个查询,返回一套符合查询条件的行,发现这些行因为其它最近提交的事务而发生了改变。

SQL 事务隔离级别
隔离级别
脏读
不可重复读
幻读
读未提交
可能
可能
可能
读已提交
不可能
可能
可能
可重复读
不可能
不可能
可能
可串行化
不可能
不可能
不可能

二、测试准备
1. 测试环境
OS:Windows 7
DBMS:SQL Server 2008 Express


2. 测试观点
考虑以下因素组合的的情况下2个并发事务的相互影响。
1)事务隔离级别
  读未提交,读已提交,可重复读,可串行化
2)DML语句
  select,insert,update,delete
3)访问对象
 表,同一行,不同行

3. 数据定义
使用下面具有代性的表定义,并插入两条记录
create table tb1(id int primary key,name varchar(30));
insert into tb1 values(1,'a');
insert into tb1 values(2,'a');

三、测试方法
同时开2个终端并开始事务块,先在终端1上执行一个SQL语句,不提交,然后在终端2上执行第二个SQL语句,观察第二个SQL语句是否受第一个SQL语句影响。
先执行的SQL语句根据作用范围,分为单行和整表的查询/更新/删除(单行处理一般会走索引,而整表处理则顺序扫描);后执行的SQL语句根据前一SQL语句作用范围,分为同一行,不同行和整表的操作。
在不同的事务隔离级别下,分别做以上测试。

先执行的SQL语句如下:
 名称  SQL语句
 单行查询  select * from tb1 where id = 1
 整表查询  select * from tb1
 插入  insert into tb1 values(5,'b')
 单行更新  update tb1 set name = 'b' where id = 1
 整表更新  update tb1 set name = 'b'
 单行删除  delete from tb1 where id = 1
 整表删除  delete from tb1

后执行的SQL语句,根据先执行的SQL语句有所不同。先执行的SQL语句是单行查询、单行更新或单行删除时,后执行的SQL语句如下:
 名称  SQL语句
 同一行查询  select * from tb1 where id = 1
 非同行查询  select * from tb1 where id = 2
 整表查询  select * from tb1
 同一行插入  insert into tb1 values(1,'c')
 非同行插入  insert into tb1 values(6,'c')
 同一行更新  update tb1 set name = 'c' where id = 1
 非同行更新  update tb1 set name = 'c' where id = 2
 整表更新  update tb1 set name = 'c'
 同一行删除  delete from tb1 where id = 1
 非同行删除  delete from tb1 where id = 2
 整表删除  delete from tb1

先执行的SQL语句是整表查询、整表更新或整表删除时,非同行的查询、更新和删除的对象为不存在的行,其他和先行SQL是单行操作时相同。
名称 SQL语句
同一行查询 select * from tb1 where id = 1
非同行查询 select * from tb1 where id = 100
整表查询 select * from tb1
同一行插入 insert into tb1 values(1,'c')
非同行插入 insert into tb1 values(6,'c')
同一行更新 update tb1 set name = 'c' where id = 1
非同行更新 update tb1 set name = 'c' where id = 100
整表更新 update tb1 set name = 'c'
同一行删除 delete from tb1 where id = 1
非同行删除 delete from tb1 where id = 100
整表删除 delete from tb1
注:黄色代表和先行SQL是单行操作时不同的地方

先执行的SQL语句是插入时,同一行代表和插入语句的id相同,非同行代表完全新的行。
名称 SQL语句
同一行查询 select * from tb1 where id = 5
非同行查询 select * from tb1 where id = 2
整表查询 select * from tb1
同一行插入 insert into tb1 values(5,'c')
非同行插入 insert into tb1 values(6,'c')
同一行更新 update tb1 set name = 'c' where id = 5
非同行更新 update tb1 set name = 'c' where id = 2
整表更新 update tb1 set name = 'c'
同一行删除 delete from tb1 where id = 5
非同行删除 delete from tb1 where id = 2
整表删除 delete from tb1
注:黄色代表和先行SQL是单行操作时不同的地方

四、测试结果
4种隔离级别下的测试结果如下

读未提交:

先执行SQL\后执行SQL
同一行查询 非同行查询 整表查询 同一行插入 非同行插入 同一行更新 非同行更新 整表更新 同一行删除 非同行删除 整表删除
单行查询 OK OK OK 主键冲突 OK OK OK OK OK OK OK
整表查询 OK OK OK 主键冲突 OK OK OK OK OK OK OK
插入

OK(*)

OK OK(*) 等待(*) OK 等待(*) OK 等待(*) 等待(*) OK 等待(*)
单行更新 OK(*) OK OK(*) 等待(*) OK 等待(*) OK 等待(*) 等待(*) OK 等待(*)
整表更新 OK(*) OK OK(*) 等待(*) OK 等待(*) OK 等待(*) 等待(*) OK 等待(*)
单行删除 OK(*) OK OK(*) 等待(*) OK 等待(*) OK 等待(*) 等待(*) OK 等待(*)
整表删除 OK(*) OK OK(*) 等待(*) OK 等待(*) OK 等待(*) 等待(*) OK 等待(*)
OK(*):基于更新后数据,即发生了脏读
等待(*):如果先行的SQL提交,则基于更新后的数据,否则基于原来的数据

读已提交:

先执行SQL\后执行SQL
同一行查询 非同行查询 整表查询 同一行插入 非同行插入 同一行更新 非同行更新 整表更新 同一行删除 非同行删除 整表删除
单行查询 OK OK OK 主键冲突 OK OK OK OK OK OK OK
整表查询 OK OK OK 主键冲突 OK OK OK OK OK OK OK
插入

等待(*)

OK 等待(*) 等待(*) OK 等待(*) OK 等待(*) 等待(*) OK 等待(*)
单行更新

等待(*)

OK

等待(*)

等待(*) OK 等待(*) OK 等待(*) 等待(*) OK 等待(*)
整表更新

等待(*)

OK

等待(*)

等待(*) OK 等待(*) OK 等待(*) 等待(*) OK 等待(*)
单行删除

等待(*)

OK

等待(*)

等待(*) OK 等待(*) OK 等待(*) 等待(*) OK 等待(*)
整表删除

等待(*)

OK

等待(*)

等待(*) OK 等待(*) OK 等待(*) 等待(*) OK 等待(*)
等待(*):如果先行的SQL提交,则基于更新后的数据,否则基于原来的数据
注:黄色代表和读未提交不同的地方

可重复读:

先执行SQL\后执行SQL
同一行查询 非同行查询 整表查询 同一行插入 非同行插入 同一行更新 非同行更新 整表更新 同一行删除 非同行删除 整表删除
单行查询 OK OK OK 等待 OK 等待 OK 等待 等待 OK 等待
整表查询 OK OK OK 等待 OK 等待 OK 等待 等待 OK 等待
插入

等待(*)

OK 等待(*) 等待(*) OK 等待(*) OK 等待(*) 等待(*) OK 等待(*)
单行更新

等待(*)

OK

等待(*)

等待(*) OK 等待(*)

OK

等待(*) 等待(*) OK 等待(*)
整表更新

等待(*)

OK

等待(*)

等待(*) OK 等待(*)

OK

等待(*) 等待(*) OK 等待(*)
单行删除

等待(*)

OK

等待(*)

等待(*) OK 等待(*) OK 等待(*) 等待(*) OK 等待(*)
整表删除

等待(*)

OK

等待(*)

等待(*) OK 等待(*) OK 等待(*) 等待(*) OK 等待(*)
等待(*):如果先行的SQL提交,则基于更新后的数据,否则基于原来的数据
注:黄色代表和读已提交不同的地方

可串行化:

先执行SQL\后执行SQL
同一行查询 非同行查询 整表查询  同一行插入  非同行插入 同一行更新 非同行更新 整表更新 同一行删除 非同行删除 整表删除
单行查询 OK OK OK 等待 OK 等待 OK 等待 等待 OK 等待
整表查询 OK OK OK 等待 等待 等待 等待 等待 等待 等待 等待
插入

等待(*)

OK 等待(*) 等待(*) OK 等待(*) OK 等待(*) 等待(*) OK 等待(*)
单行更新 等待(*) OK 等待(*) 等待(*) OK 等待(*) OK 等待(*) 等待(*) OK 等待(*)
整表更新 等待(*) OK 等待(*) 等待(*) (*) 等待(*) (*) 等待(*) 等待(*) (*) 等待(*)
单行删除 等待(*) OK 等待(*) 等待(*) OK 等待(*) OK 等待(*) 等待(*) OK 等待(*)
整表删除 等待(*) OK 等待(*) 等待(*) (*) 等待(*) (*) 等待(*) 等待(*) (*) 等待(*)
等待(*):如果先行的SQL提交,则基于更新后的数据,否则基于原来的数据
注:黄色代表和可重复读不同的地方

不难看出上述4种隔离级别是通过锁实现的,SQL Server中还支持基于行版本控制(MVCC)的隔离级别。启用这个功能需要打开下面2个选项

  1. ALTER DATABASE dbname SET ALLOW_SNAPSHOT_ISOLATION ON
  2. ALTER DATABASE dbname SET READ_COMMITTED_SNAPSHOT ON
注:详见 http://msdn.microsoft.com/zh-cn/library/ms175095(v=SQL.100).aspx

打开READ_COMMITTED_SNAPSHOT开关后,读已提交的实现方式及外部表现就不一样了。
读已提交(READ_COMMITTED_SNAPSHOT=ON):

先执行SQL\后执行SQL
同一行查询 非同行查询 整表查询 同一行插入 非同行插入 同一行更新 非同行更新 整表更新 同一行删除 非同行删除 整表删除
单行查询 OK OK OK 主键冲突 OK OK OK OK OK OK OK
整表查询 OK OK OK 主键冲突 OK OK OK OK OK OK OK
插入

OK(**)

OK

OK(**)

等待(*) OK 等待(*) OK 等待(*) 等待(*) OK 等待(*)
单行更新

OK(**)

OK

OK(**)

等待(*) OK 等待(*) OK 等待(*) 等待(*) OK 等待(*)
整表更新

OK(**)

OK

OK(**)

等待(*) OK 等待(*) OK 等待(*) 等待(*) OK 等待(*)
单行删除

OK(**)

OK

OK(**)

等待(*) OK 等待(*) OK 等待(*) 等待(*) OK 等待(*)
整表删除

OK(**)

OK

OK(**)

等待(*) OK 等待(*) OK 等待(*) 等待(*) OK 等待(*)
OK(**):基于更新前的数据,即看到是查询时的快照
等待(*):
如果先行的SQL提交,则基于更新后的数据,否则基于原来的数据
注:黄色代表和READ_COMMITTED_SNAPSHOT=OFF时的读已提交不同的地方

打开ALLOW_SNAPSHOT_ISOLATION开关后,可以使用SQL Server扩展的一种隔离级别SNAPSHOT,也称作SI(SNAPSHOT ISOLATION)。
SNAPSHOT满足SQL规范的可串行化隔离级别定义,即脏读、不可重复读和幻读都不会出现,但SNAPSHOT并不是真正的可串行化,关于这一点准备以后详细进行说明。

  1. SET TRANSACTION ISOLATION LEVEL SNAPSHOT;

SNAPSHOT:

先执行SQL\后执行SQL
同一行查询 非同行查询 整表查询 同一行插入 非同行插入 同一行更新 非同行更新 整表更新 同一行删除 非同行删除 整表删除
单行查询 OK OK OK 主键冲突 OK OK OK OK OK OK OK
整表查询 OK OK OK 主键冲突 OK OK OK OK OK OK OK
插入

OK(***)

OK

OK(***)

等待(*) OK 等待(**) OK 等待(**) 等待(**) OK 等待(**)
单行更新

OK(***) 

 
OK

OK(***)

等待(*) OK 等待(**) OK 等待(**) 等待(**) OK 等待(**)
整表更新

OK(***)

OK

OK(***)

等待(*) OK 等待(**) OK 等待(**) 等待(**) OK 等待(**)
单行删除

OK(***)

OK

OK(***)

等待(*) OK 等待(**) OK 等待(**) 等待(**) OK 等待(**)
整表删除

OK(***)

OK

OK(***)

等待(*) OK 等待(**) OK 等待(**) 等待(**) OK 等待(**)
OK(***):基于更新前的数据,查询看到的是事务开始时的快照
等待(*):
如果先行的SQL提交,则基于更新后的数据,否则基于原来的数据
等待(**):如果先行的SQL提交,则报更新冲突的错误
注:黄色代表和READ_COMMITTED_SNAPSHOT=ON时的读已提交不同的地方


五、小结
SQL Server完整实现了SQL标准定义的4个隔离级别。并且还提供了基于MVCC的读未提交和SNAPSHOT隔离级别,可避免读和写之间的锁定提高并发性能。






相关实践学习
使用SQL语句管理索引
本次实验主要介绍如何在RDS-SQLServer数据库中,使用SQL语句管理索引。
SQL Server on Linux入门教程
SQL Server数据库一直只提供Windows下的版本。2016年微软宣布推出可运行在Linux系统下的SQL Server数据库,该版本目前还是早期预览版本。本课程主要介绍SQLServer On Linux的基本知识。 相关的阿里云产品:云数据库RDS SQL Server版 RDS SQL Server不仅拥有高可用架构和任意时间点的数据恢复功能,强力支撑各种企业应用,同时也包含了微软的License费用,减少额外支出。 了解产品详情: https://www.aliyun.com/product/rds/sqlserver
目录
打赏
0
0
0
0
50
分享
相关文章
数据库数据恢复——sql server数据库被加密的数据恢复案例
SQL server数据库数据故障: SQL server数据库被加密,无法使用。 数据库MDF、LDF、log日志文件名字被篡改。 数据库备份被加密,文件名字被篡改。
大数据新视界--大数据大厂之MySQL数据库课程设计:MySQL 数据库 SQL 语句调优方法详解(2-1)
本文深入介绍 MySQL 数据库 SQL 语句调优方法。涵盖分析查询执行计划,如使用 EXPLAIN 命令及理解关键指标;优化查询语句结构,包括避免子查询、减少函数使用、合理用索引列及避免 “OR”。还介绍了索引类型知识,如 B 树索引、哈希索引等。结合与 MySQL 数据库课程设计相关文章,强调 SQL 语句调优重要性。为提升数据库性能提供实用方法,适合数据库管理员和开发人员。
大数据新视界--大数据大厂之MySQL 数据库课程设计:MySQL 数据库 SQL 语句调优的进阶策略与实际案例(2-2)
本文延续前篇,深入探讨 MySQL 数据库 SQL 语句调优进阶策略。包括优化索引使用,介绍多种索引类型及避免索引失效等;调整数据库参数,如缓冲池、连接数和日志参数;还有分区表、垂直拆分等其他优化方法。通过实际案例分析展示调优效果。回顾与数据库课程设计相关文章,强调全面认识 MySQL 数据库重要性。为读者提供综合调优指导,确保数据库高效运行。
数据库编程:在PHP环境下使用SQL Server的方法。
看看你吧,就像一个调皮的小丑鱼在一片广阔的数据库海洋中游弋,一路上吞下大小数据如同海中的珍珠。不管有多少难关,只要记住这个流程,剩下的就只是探索未知的乐趣,沉浸在这个充满挑战的数据库海洋中。
51 16
如何优化SQL查询以提高数据库性能?
这篇文章以生动的比喻介绍了优化SQL查询的重要性及方法。它首先将未优化的SQL查询比作在自助餐厅贪多嚼不烂的行为,强调了只获取必要数据的必要性。接着,文章详细讲解了四种优化策略:**精简选择**(避免使用`SELECT *`)、**专业筛选**(利用`WHERE`缩小范围)、**高效联接**(索引和限制数据量)以及**使用索引**(加速搜索)。此外,还探讨了如何避免N+1查询问题、使用分页限制结果、理解执行计划以及定期维护数据库健康。通过这些技巧,可以显著提升数据库性能,让查询更高效流畅。
JetBrains DataGrip 2025.1 发布 - 数据库和 SQL 跨平台 IDE
JetBrains DataGrip 2025.1 (macOS, Linux, Windows) - 数据库和 SQL 跨平台 IDE
51 0
数据库数据恢复—SQL Server报错“错误 823”的数据恢复案例
SQL Server数据库附加数据库过程中比较常见的报错是“错误 823”,附加数据库失败。 如果数据库有备份则只需还原备份即可。但是如果没有备份,备份时间太久,或者其他原因导致备份不可用,那么就需要通过专业手段对数据库进行数据恢复。

热门文章

最新文章

下一篇
oss创建bucket