zoukankan      html  css  js  c++  java
  • 数据库事务及锁相关思考

      我了解数据库不同的锁,我了解不同的事务隔离级别,但是再一次生产中,服务器依然报出了死锁,这引起我的反思,先不讨论SPring transaction给我们造的轮子的问题,先探讨一下为什么会引起这次死锁?

    这次死锁主要原因是 我们再测试条件下启动了一条线程去给数据库更新一条数据,而由于当时时间段比较特殊,我们后台的Timetask也在此时启动,到底很凑巧的两条线程的两个事物同时并行的去更改数据,后来看了一下日志,是两个事物同时获得共享锁,而其中一条线程企图更新数据,尝试将共享锁上升到排它锁,而另一条线程一直没有释放锁,至此导致死锁。

      错误类型。举例来说。

    更新丢失:a事务中更新一条记录,却被b事务的更新操作给覆盖了。也就是说,a事务的操作没有生效。

    脏读:b事务中执行了修改操作:将namelee改成了tom,但是还没提交,a事务中读到到的数据是tom,当事务b回滚,name变回了lee。这样就导致了a事务中读到的tom是不存在的。

    不可重复读:事务a中读取了name字段,其值为lee。在第二次读取name字段的过程中,事务bname字段的值改成了jack,事务a第二次读到的是jack,发现与第一次读到的不一样,即两次读到的数据不一样

    幻读:假设表(字段有id主键和value)中有三条记录,分别为(1a),(2b),(3c)。a事务读取全部发现这三条,b事务新增一条记录(4d),然后提交事务。然后a事务再查询,发现还是这三条(没能读取到最新那条),于是新增一条(4d)。结果报错了,说主键重复了。可是事务a中查询到的记录明明没有id4的记录。这就是幻读。

      首先介绍锁的类型:  

    更新锁:读取表时使用更新锁,不使用共享锁,并将锁一直保持到语句或事务结束。Updlock的优点是可以允许其他线程读取数据,并在以后更新数据,同时保证自动从上次读取数据后的数据没有被修改。

    共享锁:若事务T对数据加上了共享锁,则只允许对数据进行查询,但是不能修改,其他事务Q只能再对该数据加共享锁,不能加上排它锁,直到释放T共享锁才能加上排它锁。

      下面介绍为什么不同的隔离级别无法解决那些问题。

      理解背后的原因:

      一级封锁协议:一级封锁协议是指,事务T在修改数据R之前必须先对其进行加X锁,直到事务T结束才释放(事务结束包括正常结束的commit 和 非正常结束rollback

    一级封锁协议可以方式丢失更新的问题,事务t1想要对数据进行修改,那么先得加锁。这时事务t2也想对数据r进行修改,那得先给RX锁。但是由于数据R已经被上了X锁,此时事务T2无法对其进行上X锁,因此只能一直等待,直到数据R上的X锁被释放。才能给它上X锁,才能进行修改操作,如此解决了丢失更新的问题。

    但是为什么没能解决不可重复读和脏读的问题呢?

    思考:如果选择了封锁协议,那么任何其他线程企图访问已经被X锁锁定的资源时,只能进行读取而不能进行修改,这时就有一个特别的“问题”,如果资源被X锁锁定时被更新,然后又回滚,这时其他线程一直在读,发现这个数据在事务中是一直在变的。这时就造成了不可重复读了和脏读了。

       二级封锁协议:在一级封锁协议的基础上增加事务T在读取R之前必须对其加S锁,读完才可释放S锁。

    二级封锁协议是在一级封锁协议基础上的,因此它必然能解决丢失更新的问题,除此之外呢,还能解决脏读的问题,为什么呢?因为如果我们按照二级封锁协议规定来读数R,如果一级封锁没有放开锁,二级锁永远没办法加上。所以一直无法读,因此避开了脏读的问题,但是还是没办法解决不可重复读的问题,但在我看来,不可重复读本身不能算是问题。因为你不能限值一个数据在你读一次和读两次的数据完全一致,这不公平,也不科学。所以引申到read-repeat这个事务隔离级别,这个事务隔离级别是要干什么事呢?

      三级封锁协议:原则上,理论上使用锁实现

    三级封锁协议是指,在以及封锁协议的基础上增加事务T在读取数据R之前必须先对其加S锁,直到事务结束才释放。

    三级封锁协议是在一级封锁协议的基础上的,因此必然能解决丢失更新问题,另外,三级封锁协议规定,在增加事务T在读取数据R之前必须先对其加S锁,这和二级封锁协议一样,因此三级封锁协议也能解决脏读问题。除此之外,它还规定加上的S锁直到结束才释放。这就解决了不可重复读问题。继续以上面的例子来说。如果事务a想要执行查询操作,那么得先给该数据加S锁,没问题,加锁后执行了查询操作,读到的是(1‘a’),读完后没有立马释放S锁。这时事务b想对该记录进行修改,那么先加R锁吧,发现,该记录已经被上了S锁,因此,没办法加R锁,因此只能一直等待,直到S锁被释放。如此就保证了事务a的两次查询之间不会插入另一个事务对同一记录的更新操作。这样一来,事务a两次读取的记录就一定会是一样的。如此,就避免了不可重复读问题。

      

      所谓锁:分两种大类

    1. 乐观锁
    2. 悲观锁

    1.数据库乐观锁的实现是通过自定义version实现,适用于并发更改同一行数据很少的情况

    2.而我们传统中的对数据库锁的认知都是悲观锁,就是操作之前加锁。

    1、共享锁,共享锁使得不同session可以共享同一批次的数据,读不影响。但写操作时需要将共享锁上升至排他锁。

    2、排它锁,排它锁保证当前锁定的数据只能由当前session进行修改,其他session可以读。不能加任何锁,包括共享锁

    3、更新锁,同一批数据只允许加一个更新锁,加更新锁的数据允许其他session读取数据,也就是说,其他线程始终无法将共享锁上升至排他锁或者更新锁,而更新锁可以自然的上升到排他锁。可以避免共享锁互相等待的问题

    另外,由于索引的关系,不同的数据库在查询时如果指定某一行数据,可能查询会导致整张表加锁。

  • 相关阅读:
    火爆全网的合成大西瓜小游戏魔改版大全
    [Qt]cmake下Qt隐藏console的窗口
    c# WebBrowser控制台输出执行js后的网页内容
    好的编程习惯是减少bug最有效的方法
    创建线程 出现SIGSEGV crash
    linux下进程创建/僵尸进程/孤儿进程
    C++实现不可被继承的类
    程序并发概述
    C++ vector实现原理
    C++深拷贝和浅拷贝
  • 原文地址:https://www.cnblogs.com/yxs98/p/11577938.html
Copyright © 2011-2022 走看看