zoukankan      html  css  js  c++  java
  • 关于数据库事务和锁的一些分析

    本文将针对以下几个问题给大家解答:

    1)什么是事务?事务有哪些特性?

    2)不同隔离级别的事务,有什么区别?

    3)了解一下数据库锁:共享锁,更新锁,排它锁

    4)数据库事务和锁之间有什么关系?

    5)拓展:什么是分布式事务?有哪些解决方案?

    事务

    通常是指包含了多个数据库执行操作(select,update,delete,insert)的一个程序执行单元。

    事务的四大特性ACID:

    1.原子性(Atomicity):事务中所有数据库操作要么全部执行成功,要么全部执行失败。

    2.一致性(Consistency):事务执行前后数据库状态保持一致。比如付款操作:A账户减少100元,相应的B账户增加100元。

    3.隔离性(Isolation):事务与事务之间相互隔离,互不影响,保证了事务之间数据处理的独立性。

    4.持久性(Durability):事务一旦提交成功,那么对数据库数据的影响必将持久化到数据库中,不会因为外在环境,比如断电,服务器宕机等因素而影响对数据库数据的变化。事务提交成功之后,会首先记录到数据库日志文件中,即使断电重启后,也会继续读取日志完成事务操作。

    事务分类:根据事务中涉及的数据库操作实例数量区分

        本地事务:事务仅针对同一个数据实例进行操作, 例如:.Net中具体实现如SQLtransaction 

        分布式事务:事务中针对多个数据库实例进行操作。例如:.Net中的DTC分布式事务,具体实现TransactionScope

    事务的隔离性,将事务分为不同隔离级别

    1.未提交读(Read UnCommitted) :允许该事务读取其他事务未提交的数据。   

       存在问题:脏读

    2.已提交读 (Read Committed):允许该事务读取其他事务已提交的数据。

       解决了脏读的问题

       存在问题:不可重复读,事务中前后读取数据不一致。

    3.可重复读 (Repeatable Read):同一个查询,保证该事务读取前后数据一致。

       解决了不可重复度的问题

       存在问题:幻读 ,可能读取到其他事务新增的数据。

    4.串行读(Serializable):要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。

       解决了幻读的问题

    SQLServer数据库事务语法:

    SET TRANSATION ISOLATION LEVEL READ UNCOMMITTED--设置事务隔离级别
    BEGIN TRANSACTION
    ...
    ...--事务执行内容
    ...
    COMMIT/ROLLBACK--提交或者回滚

    数据库锁
    1.共享锁(S)

    1)保护读取的数据,读取的过程中,其他并发事务不能修改,删除数据,可以并发读取。

    2)当事务隔离级别低于“可重复读”时,一旦数据读取结束,立即释放共享锁,和事务是否结束无关。

    2.排它锁(X)

    1)保护修改的数据,获取当前被修改资源的锁,该资源同一时刻只能被一个排它锁占有,不允许其他并发事务操作该资源(读取和修改)。

    2)排它锁和共享锁不能兼容。当修改资源时,会自动由共享锁升级为排它锁,因此必须等待资源释放共享锁,才能获得排它锁。

    3)如果排它锁存在于事务中,需等事务结束才能释放锁。

    3.更新锁(U)

    1)更新锁时介于共享锁和排它锁中间的混合。更新锁通过uplock手动添加,分两个阶段:1.在修改操作之前,通过更新锁获取资源对象 ,其他事务线程则只能读取不能操作  2.然后执行修改操作,将更新锁升级为排它锁。在修改操作前实现对资源锁定,避免了死锁。

    2)更新锁和共享锁可以共存,因此使用更新锁比使用排它锁解决死锁问题,性能更优。

    3)持有更新锁的资源,允许被其他并发事务select。

    4)如果更新存在于事务中,需等事务结束才能释放更新锁。

    事务与锁之间的关系

    首先我们说事务和数据库锁之间没有必然的联系,但是事务的执行时间会影响锁持有的时间,间接影响了数据执行效率。

    1.通常在执行select查询的时候会持有共享锁,查询一旦执行结束,则立即释放共享锁。如果在事务中执行查询,且隔离级别低于可重复读,即使事务没有执行结束,也不会影响共享锁的释放。

    2.更新锁和排它锁,通常在语句执行结束后会立即释放锁资源。如果在事务中执行相关锁操作,需等待事务执行结束,才能释放锁。

    3.update语句天然就会持有排它锁,即使不在事务中执行update操作也会持有锁。

    4.事务隔离级别未提交读(Read UnCommitted)等同于select查询添加with(nolock),允许读取“脏数据”

    语法:

    select * from sys.objects with(nolock) where name='sysrscols';

    5.事务隔离级别中的未提交读、已提交读和可重复读都是行级别锁,对条件范围内的行数据持有锁。

    6.事务如何通过隔离级别处理并发,以“可重复读”隔离级别为例:

     1)事务A,默认隔离级别“已提交读”,查询操作5秒后执行更新操作

    BEGIN TRAN
    SELECT * FROM [ORDER] WHERE ID='10'
    WAITFOR DELAY '00:00:05'
    UPDATE [ORDER] SET PRICE=30 WHERE ID='10'
    COMMIT TRAN

    2)事务B,隔离级别“可重复读”,前后两次查询间隔10秒

    SET TRAN ISOLATION LEVEL SERIALIZABLE
    BEGIN TRAN
    SELECT * FROM [ORDER] WHERE ID='10'
    WAITFOR DELAY '00:00:10'
    SELECT * FROM [ORDER] WHERE ID='10'
    COMMIT TRAN

    3)启动事务A后,立即启动事务B,事务具体执行顺序如下:

     a)事务A执行查询,并持有ID=10数据的共享锁,查询结束后释放锁,并等待5秒

     b)事务B执行查询,并持有ID=10数据的共享锁,由于隔离级别是可重复读,因此查询结束后也一直持有共享锁资源,并等待10秒。

     c)事务A5秒等待结束后,执行update申请持有ID=10数据的排它锁,此时事务B已经持有了共享锁,由于共享锁和排它锁互斥,所以事务A申请排它锁失败,继续等待事务B释放锁资源。这里其实也正是“可重复读”隔离级别解决“不可能重复”问题的关键。

     d)事务B10秒等待结束后,继续执行下一个select查询,并申请持有ID=10数据的共享锁,由于共享锁可以共存,事务B申请成功并完成查询,前后查询数据保持一致。

     e)事务A在事务B执行结束后,立即获取到ID=10数据的排它锁,并执行成功,事务A结束释放锁资源。

    拓展:

    锁分类:

      悲观锁:使用数据库锁机制,牺牲并发性能,保持事务一致性。

      乐观锁:不使用数据库锁机制,通常可通过操作前后校验数据的方式,比如字段中增加一个版本号version,如果更新前后版本号一致,则执行成功 否则返回错误提示,也能保证事务的一致性。

    分布式事务

    本地事务都是操作的本地单数据库,分布式事务中不同的操作可能涉及多个不同服务器上的数据库实例,因此本地事务不再满足。

    解决方案:

    1.两阶段提交(2PC)
    准备阶段:事务协调者询问事务中的每个数据库参与者是否都执行事务成功,如果成功则进入下阶段。
    提交阶段:事务协调者通知事务中每个参与者提交执行操作。
    目前.Net中分布式事务支持TransactionScope 需启动MSDTC服务,即事务协调者。
    特点:需要事务中涉及的每个参与者反馈成功才能最终提交,满足事务一致性,但是阻塞较长。

    2.事务补偿机制(TCC)
    该机制将事务中每个操作都注册对应的确认和补偿操作:分为三个阶段
    1.try阶段:主要是对业务系统做检测和预留
    2.confirm阶段:做确认提交,一旦try阶段成功,则默认confirm成功
    3.cancel取消阶段:如果步骤执行失败,执行回滚。

    3.本地事务+MQ消息
    将分布式事务分为多个本地事务,不同服务器上的本地事务通过MQ消息发送。MQ分发的内容需要通过中间消息表进行记录并分发。满足事务最终一致性原则。
    如:一个分布式事务中包含A,B两个不同数据库服务器上的操作,A操作执行本地事务+消息表记录B操作,MQ发送消息表信息,如果B服务器接收到信息,返回消息接收成功。那么A本地事务就会执行结束并提交。至于B服务器是否执行成功,A服务器不再关心。B服务器接收到消息之后,会在B本地事务执行,如果执行失败,则会一直重试,直到执行成功,以保持事务最终一致性。
    其中如果A发送队列失败,也会重试发送。

  • 相关阅读:
    [转]lftp的致命错误:证书验证:不信任
    github每次push都需要密码以及用户名的解决办法
    Fedora最小化安装后没有ifconfig命令
    [转载]MySql常用命令总结
    chrome浏览器强制采用https加密链接
    红帽系列linux自行配置本地yum源
    linux 下dd命令直接清除分区表(不用再fdisk一个一个的删除啦)
    linux分区工具fdisk的使用
    Java多线程实现......(1,继承Thread类)
    第一篇文章--我为什么要写博客?
  • 原文地址:https://www.cnblogs.com/chenxf1117/p/15071559.html
Copyright © 2011-2022 走看看