[聚合文章] MSSQL-并发控制-2-Isolation

SQL Server 2017-11-27 17 阅读
 
    如果转载,请注明博文来源: www.cnblogs.com/xinysu/   ,版权归 博客园 苏家小萝卜 所有。望各位支持!
 


       MySQL通过MVCC和锁来实现并发控制,在4个隔离级别中,读写数据方式及加锁方式有所不同,以满足不同的业务需求。

    而在MSSQL中,也是通过锁和MVCC的行版本来实现并发控制。
    每个事务中,锁的类型、级别、加锁、释放的情况,由事务的隔离级别控制,在MSSQL中,有6个隔离级别,不同的隔离级别对锁的应用不一样。而这两个隔离级别中,有2个应用 MVCC的机制,也就是 快照类的隔离级别:Read Commmitted Snapshot 跟 Snapshot。

1 并发控制理论

    在MSSQL中,经常用到的并发控制理论是 悲观并发控制跟乐观并发控制。

1.1 悲观并发控制

    悲观并发,默认在事务操作过程中,一定会有其他事务跟它争夺资源,所以在事务操作过程中,会根据不同的情况对数据添加锁,避免操作期间其他事务对该数据的修改或读取,保证数据的一致性。
    悲观并发控制,由于纳入了锁机制,很大程度会影响到并发规模。主要应用于数据频繁修改、并且回滚事务的成本要大于锁数据的成本 的系统中

1.2 乐观并发控制

    乐观控制,默认事务在读取数据的时候,其他事务并没有在操作这些数据,所以不会加锁,直接修改数据,修改后查看读取数据期间是否有其他用户也修改了数据,如果有,则回滚本身的修改事务。
    乐观并发控制,应用于数据修改不频繁、并且 回滚事务成本要小于锁数据成本 的系统中 

2 隔离级别

    在每一个事务中,都指定了一个隔离级别,该隔离级别定义了这个事务跟其他事务之间的隔离程度。
 
    在MSSQL中,有6种隔离级别,4个常规隔离级别跟2个快照隔离级别:Read UnCommitted、Read Committed、Read Commmitted (行版本)、Read Repeattable、Snapshot跟Read Serializeble。Read Commmitted (行版本)跟Snapshot 可能接触情况比较少,不过仍会说明。
 
    在MySQL中,默认的隔离级别是RR,而在SQL SERVER中,默认的隔离级别是RC,读已提交。

2.1 隔离级别说明

    如何设置整个数据库的默认隔离级别?   
 
    数据不一致的说明详见之前博文:http://www.cnblogs.com/xinysu/p/7260227.html 中的第四章:数据不一致情况。
 
    下文中说S锁,并不是全部加锁过程(MSSQL中还是IS锁的申请)。
  1. Read UnCommitted
    • 简称 RU,读未提交记录,始终是读最新记录
    • 可能存在脏读、不可重复读、幻读等问题
    • 读的过程不加S锁,等同于 SELECT * FROM tbname with(nolock)
  2. Read Committed
    • 简称 RC ,读已提交记录
    • 可能存在不可重复读、幻读等问题
    • 读的过程加 S锁,无论事务是否结束,SELECT 语句一旦结束,立马释放S锁,不会等到事务结束才释放锁,遵循的是 Strict 2-PL
  3. Read Commmitted (行版本)
    • 简称 RCSI
    • 应用MVCC原理,版本读,读已提交记录,但是读取到的不一定是最新的记录
    • 同个事务中,读取数据都是同一个版本
    • 不存在脏读、不可重复读问题,可能存在幻读问题
    • 行版本控制隔离级别 中的版本数据,不存在与数据库本身,而是存在 tempdb ,下文会详细描述这一隔离级别
  4. Read Repeattable
    • 简称 RR ,可重复读记录
    • 可能存在幻读等问题
    • 读的过程加S锁,直到事务结束,才释放S锁,遵循的是 Stong Strict 2-PL
  5. Snapshot
    • 简称 SI
    • 下文会详细描述这一隔离级别
  6. Read Serializeble
    • 简称 RS,序列化读记录
    • 不存在 脏读、不可重复读、幻读等问题
    • 读的过程中除了添加S锁,还添加范围锁;修改数据的过程中,除了添加 X 锁,也会添加范围锁,避免在符合条件的数据在操作过程中,有其他符合条件的数据INSERT进来
    • 并发度最差,除非明确业务需求及性能影响才使用,曾经遇到过某个短信业务的框架默认使用这个隔离级别,上线后爆发死锁上K个,马上分析紧急修复....

2.2 Read Commmitted Snapshot Isolation 与 Snapshot Isolation

    Read Commmitted Snapshot Isolation 使用行版本控制语句级的快照,在事务中当数据发生修改或者删除时,调用写入复制机制,保证写入的行数据的旧版本满足事务操作前的一致性。 RCSI 保证的是语句级的 读一致性。
    Snapshot Isolation 使用行版本控制事务级的快照,当事务开始的时候,调用写入复制机制。 SI 保证的是事务级 的读取一致性。
         
     如何管理行版本信息呢?
     两者的行版本的信息均存储在tempdb数据库内,并非存储在本身的数据库,这就要求tempdb要有足够的空间存储版本信息,如果tempdb空间不足,则行版本写入失败,造成该隔离级别无法正常使用。
     存储引擎对使用 RCSI 或者 SI 隔离级别的事务,在 SI事务开始的时候,分配一个事务序列号 XLN,每次分配递增1,以此实现事务级的一致性,这里注意 RCSI的 事务序列号 并不是一个事务一个序列号,而是事务内每条SQL一个事务序列号,以此来实现语句级别的快照。这两个隔离级别下,需要维护所有执行过数据修改的逻辑副本(即行版本),这些逻辑副本存储在tempdb内,每个逻辑副本(行版本)都有标记本次的事务的事务序列号XLN。即 最新的行值存储在当前的数据库中,而历史行版本信息包括最新版本,存储在tempdb中。这里注意一下,事务内的修改数据写行版本信息的时候,先写入到缓存池中,在刷新到tempdb文件,避免性能造成太大的影响。
    
    这个时候,可能会问?那岂不是tempdb要存储非常多的历史版本数据,有没有删除机制呢?
    这个是有的,一方面,行版本信息不会即时删除,因为要保证基于行版本控制隔离级别下运行的事务要求,保证并行的事务如果正在使用tempdb的行版本信息 不会受到影响。另一方面,数据库的存储引擎 会跟踪最早可用的事务序列号,然后定期删除比序列号更小的 XLN的所有行版本。
      
      如何读取行版本信息呢?
      两个快照隔离级别下的 的事务读数据的时候,不会获取正在读取数据上的共享锁,因此不会堵塞正在修改的事务,由于减少了锁的申请及数量,可以提供其DB并发能力。不过会获取所在表格的架构锁,如果表格正在发现架构修改(如列增加修改等),则会被堵塞。
      如何读取合适的行版本,RCSI 跟 SI 之间是有区别的。
      RCSI:每次启动语句时,提交所有数据,同时读取tempdb中的最新事务序列,这使 RCSI 下事务内的每个语句 都可以查看每个语句启动时存在的最新数据的快照,也就是 事务内多个SQL查询间隙中有其他事务修改了数据,那么同个事务的多次相同SQL查询结果就会出现不一致的情况。
      SI:每次启动事务时,提交所有数据,读取 最接近但低于 本身的 快照事务序列号,也就是 事务内的多个SQL 查询,读到的数据都是同一个版本,即使多次查询间隙有其他事务修改数据,读到的结果也是一致的。
 
      如何修改行版本信息呢 ?
      在使用 RCSI 事务中,使用阻塞性扫描(其中读取数据值时将在数据行上采用更新锁(U 锁)完成选择要更新的行,满足条件的行记录将升级更新锁到排它锁,注意,这里扫描的不是tempdb里边的行版本信息,而是实际数据库里边的最新行记录,修改数据的机制跟 RC 相同。 如果数据行不符合更新条件,则在该行上将释放更新锁,同时锁定下一行并对其进行扫描。持有锁之后,则进行数据更新,事务结束后,释放锁。
 
      在使用 SI 事务中,对数据修改采用乐观方法:使用行版本的数据,进行数据修改,直到数据修改完成是,才获取实际数据上的锁, 当数据行符合更新标准时,则提交修改的数据行。 如果数据行已在快照事务以外修改,则将出现更新冲突,同时快照事务也将终止。 更新冲突由数据库引擎处理,无法禁用更新冲突检测。
 
      从简单的SQL来分析,WHERE条件均为主键(仅为个人测试推测):