zoukankan      html  css  js  c++  java
  • Sql Server并发和事务

    锁的作用范围通常在事务中,事务是建立在并发模式下。

    从SQL Server 2005开始,加入了一种新的并发模式-----乐观并发。不管使用哪种并发模式,如果多个会话同时修改相同的数据,都会产生资源争用,然后引发一系列的问题。

    1.存在的读现象:包括脏读、不可重复读和幻读。

    2.丢失更新:一个会话的修改效果被另外一个会话意外覆盖

    3.过量的锁定:过量的锁定会导致阻塞,导致资源压力和终端用户的响应延时

    4.死锁:最少两个会话互相阻塞对方,引发死锁。SQL Server 尽可能自动侦测并干预死锁,最后给客户端返回1205错误

    绝大部分基于锁的阻塞问题都可以通过优化查询、调整索引、修改隔离级别及表结构的设计来大大降低

    一:悲观并发和乐观并发

    默认情况下,SQLServer 都会采用悲观模式来应对并发。大量的并发操作,如果不使用悲观并发来防止多个会话对数据同时修改,会造成数据不一致。在该模式下,将会通过加锁的形式保护正在被读取的事务,以保证这些数据在读取过程中不被其他会话修改。在会话修改数据的过程中也会加锁,防止其他会话读取或者修改对应的数据。也就是在悲观模式下,读操作阻塞写操作,写操作阻塞读写操作。数据库中所有操作都会加锁。

    对于乐观并发,SQL Server 会假设只有少量的冲突发生。其默认的机制就是使用快照的行版本技术,把行版本存放在TempDB数据库中

    在两种并发模式下,SQL Server 都会通过使用特定的事务隔离级别来协调事务间的争用问题。不同的隔离级别会申请不同的锁,也会影响锁的持有时间,越高的隔离级别,锁持有的时间越长,越能避免资源争用和数据的不一致性,但是并发度也越低。

    基于读写,写写互相阻塞的特性(读读不互相阻塞),SQL Server中普遍存在锁定及阻塞情况,随着并发量和数量的逐步增长,其潜在的问题将会凸显。锁定的持续时间及范围越来越大,数据库的总体性能也会越来越低。

    悲观并发对所有行为都加锁,怕在操作过程中意外影响,所以被称为悲观模式。甚至有点独占的意味。在这种情况下,读写是互相阻塞的。乐观并发通过把悲观并发在读写时加S锁的行为移到TempDB的行版本存储中,避免了读阻塞写的操作,从而把读写的资源争用降到最低,并尽量实现读操作不阻塞写操作,写操作永远阻塞读操作。

    二:事务

    不管使用悲观并发还是乐观并发,都会涉及事务这个概念,事务就是一个操作单元。这个操作可能是一行UPDATE语句,也可能是异常复杂的一系列增删改操作。

    1.Atomicity,原子性。一个事务被当作单独的工作单元,不管事务内有什么东西,都被认为是一个整体。

    2.Consistency,一致性。一个事务的执行前后状态都应该是一个逻辑一致性状态。

    3.Isolation,隔离性。事务的内部不应该和其他事务有交互。

    4.Durability,持久性,一旦事务完成,它的效果应该是永久的,不管是什么情况 ,包括系统关闭,都需要保证事务的结果。

    SQL Server 默认保证其中3个特性:原子性、一致性、和持久性。如果要保证隔离性,就需要使用比默认级别更高级的隔离级别。

    (1).事务作用域

    SQL Server 事务有四类作用域,其中两种(显示事务、自动提交事务)是默认的。另外两种(隐式事务、批范围事务)是特定情况下才用的。

    一:自动提交事务

    自动提交事务是一个单独的数据修改操作。任何INSERT、UPDATE、DELETE、MERGE、BULK INSERT等操作,都属于自动提交事务。一个单独的UPDATE语句,不管其影响行数是1行还是100万行,都会当做一个原子操作。在其中发生故障,SQL Server 会回滚这些操作,就当没有发生过一样。

    if DB_ID('Demo_Isolation')is not null
    drop database Demo_Isolation
    create database Demo_Isolation
    go
    use Demo_Isolation
    if OBJECT_ID('Demo_Table','U') is not null
    drop table Demo_Table
    create table Demo_Table
    (
       id int identity(1,1),
       name nvarchar(32)
    );
    

      检查一下当前数据库的隔离级别。

    dbcc useroptions
    

      

    insert into dbo.Demo_Table(name)
    select top 100000000 a.name
    from sys.columns a cross join sys.columns b
    cross join sys.columns
    

      然后马上执行关闭服务,模拟系统故障。

    insert into dbo.Demo_Table(name)
    select top 100000000 a.name
    from sys.columns a cross join sys.columns b
    cross join sys.columns c
    rollback
    

      关闭窗体,没有办法强制回滚,只有在系统故障时事务才会回滚

    (2)显式事务

    显式事务是以BEGIN TRANSACTION开头、以COMMIT TRANSACTION或者ROLLBACK TRANSACTION结尾的、包含有任意数据量语句的代码块。这类事务是最常见的事务类型

    (3)隐式事务

    隐式事务必须在会话开始时,使用SET IMPLICIT_TRANSACTIONS ON 开启。不需要使用Begin tran作为开头,但是必须显示使用COMMIT/ROLLBACK TRAN结束事务。不建议使用这种方式,尽可能使用显示事务来定义

    (4)批范围事务

    用于在客户端连接字符串中使用了Multiple Active Result Sets(MARS)选项的事务。SQL Server会回滚所有以BEGIN TRAN开头但是不包含COMMIT TRAN的事务,目的是避免应用程序死锁。

    2.事务隔离级别

    每一个事务都运行在一个特定的事务隔离级别中,这个由会话的隔离级别决定。通过隔离级别,SQL Server 会决定锁的持有时间。如果会话/事务过程中没有使用SET TRANSACTION ISOLATION LEVEL【隔离级别】或者Lock hints指定,默认会使用当前数据库的隔离级别,通常为READ COMMITTED。

    (1)READ UNCOMMITTED:允许所有脏读,不可重复读和幻读。

    (2)READ COMMITTED:防止脏读,但允许不可重复读和幻读。

    (3)REPEATABLE READ:防止脏读和不可重复读,但允许幻读。

    (4)SERIALIZABLE:防止所有读现象

    上面的4类隔离隔离级别中,除了READ COMMITTED(其中基于快照的一种)是乐观并发之外,其他都是悲观并发。在这几种隔离级别下,SQL Server会申请共享锁和排他锁,以避免当前事务正在读取的数据被另一个事务更改。从SQL Server2005开始,添加了新的乐观并发隔离级别,称为SNAPSHOT隔离,其中READ COMMITTED也加入了一种新的隔离(READ_COMMITTED_SNAPSHOT),这两种隔离级别不需要加入共享锁,能够重一定程度上增强并发性。

    隔离级别可以通过在会话的开端使用SET TRANSACTION ISOLATION LEVEL【隔离级别】来修改SQL Server的默认设置。也可以在语句中使用lock hints来修改。

    3.需要预防的读现象

    隔离级别存在的一个重要目的是预防3种读现象:脏读、不可重复读和幻读

    (1)脏读

    当事务运行在READ UNCOMMITTED隔离级别下时,会出现脏读。如果事务A修改了数据,但是没有提交,而另一个事务B此时又读取了这部分的数据,就很容易出现数据的非一致性状态,这种状态叫做脏读。默认情况下,SQL Server 使用了READ COMMITTED隔离级别,而这种级别不允许脏读,当事务正在修改的数据时,对其他事务是否能在提交前读取数据不会进行控制。这种特性会导致读取了非一致性的数据,俗称“脏数据”。除了数据不一致,还有另外一种潜在的风险,当事务A在READ UNCOMMITTED下进行大表扫描时,由于没有对访问的数据进行加锁,其他事务可以进行UPDATE操作。如事务A扫描了表中一半的数据,已经读取了某条数据,但是事务B中途进行了UPDATE操作,导致数据的存储位置改变,可能已经移到表的最末端。这时候事务A在完成扫描之后,实际上已经扫描了这条数据两次,甚至更多次。

    打开两个窗口:

    begin transaction;--显示事务
    update Person.Person
    set FirstName='James'
    where LastName='Jones';
    waitfor delay '00:00:05.000';
    rollback transaction;
    
    select FirstName,LastName from Person.Person where LastName='Jones';
    set transaction isolation level read uncommitted;
    select FirstName,LastName from Person.Person where LastName='Jones'
    

      

    两边的数值不同,窗口A中使用了ROLLBACK,理论上应该回滚,但是由于窗口B 使用了READ UNCOMMITTED隔离级别,会出现脏读,所以窗口B中的数据读出来和窗口A的不一样。

    (2)不可重复读

    如果在一个事务中单独两次查询相同的数据,获得不一样的结果,就称为不可重复读。(在同一个事务中的两次查询)通常是因为两次读取数据之间,有其他事务对这些数据进行了修改操作。从而导致第二次读取到不一致的数据。

    (3)幻读

    这种情况发生在带有限定词的查询中,如where条件,如一个事务中,两个相同的where 条件的select操作,反回了不同的数据行。为幻读。

    3.丢失更新

    如果一个事务进行了UPDATE操作后,在未提交或者回滚事务前,另一个事务又做了同样的操作,这样就会覆盖第一次的update,导致第一次update就像 没有出现过。所有的隔离级别都用于防止丢失更新,也就是说不允许两个事务同时更新相同的数据,以免其中一个事务的更新操作丢失。

    DECLARE @SafetyStockLevel INT = 0 ,
        @Uplift INT = 5;
    BEGIN TRAN;
    SELECT  @SafetyStockLevel = SafetyStockLevel
    FROM    Production.Product
    WHERE   ProductID = 1;
    SET @SafetyStockLevel = @SafetyStockLevel + @Uplift;
    WAITFOR DELAY '00:00:05.000';
    UPDATE  Production.Product
    SET     SafetyStockLevel = @SafetyStockLevel
    WHERE   ProductID = 1;
    SELECT  SafetyStockLevel
    FROM    Production.Product
    WHERE   ProductID = 1;
    COMMIT TRAN;
    	DECLARE @SafetyStockLevel INT = 0 ,
    		@Uplift INT = 100;
    	BEGIN TRAN;
    	SELECT  @SafetyStockLevel = SafetyStockLevel
    	FROM    Production.Product
    	WHERE   ProductID = 1;
    	SET @SafetyStockLevel = @SafetyStockLevel + @Uplift;
    	UPDATE  Production.Product
    	SET     SafetyStockLevel = @SafetyStockLevel
    	WHERE   ProductID = 1;
    	SELECT  SafetyStockLevel
    	FROM    Production.Product
    	WHERE   ProductID = 1;
    	COMMIT TRAN;
    

      

    A的代码:在waitfor前面会把数据加5,等待5s。在等待的过程中,窗口B执行Update,由于窗口A的事务为提交,所以实际修改没有发生,所以对事务之外的事务来说,数据还是1000,在B中,实际上已经对‘1000’这个值update了。两次update虽然在独立的事务内,但是由于窗口A中的update操作是在后面提交的,最终覆盖了窗口B的update,导致窗口B的操作“没有发生过”。最后提交的操作会覆盖先提交的操作。

    对于丢失更新的问题,可以使用悲观并发或者乐观并发来避免。避免丢失的操作是发生在更新前还是跟新后。悲观并发是在更新前避免,乐观并发是在更新后避免。在悲观并发模式中,读取数据时申请锁,这样一来,其他事务就无法更新了。同样,在更新时也加锁,让其他事务无法读。从而避免了丢失更新。

  • 相关阅读:
    Practice3_5_vector_sort_struct_gold_silver_bronze_playerName1
    Practice3_4_vector_sort_struct_string
    Practice3_3_vector_sort_struct
    Practice3_2_vector_sort_struct
    2017多校第9场 HDU 6170 Two strings DP
    BZOJ 3771 生成函数,FFT
    BZOJ 3028 食物 生成函数
    Codeforces Round #428 (Div. 2) 题解
    2017中国大学生程序设计竞赛
    2017多校第8场 HDU 6133 Army Formations 线段树合并
  • 原文地址:https://www.cnblogs.com/sunliyuan/p/9818804.html
Copyright © 2011-2022 走看看