一、锁

  1、数据库和操作系统一样,是一个多用户使用的共享资源。当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。加锁是实现数据库并 发控制的一个非常重要的技术。在实际应用中经常会遇到的与锁相关的异常情况,当两个事务需要一组有冲突的锁,而不能将事务继续下去的话,就会出现死锁,严重影响应用的正常执行。

  2、在数据库中有两种基本的锁类型:排它锁(Exclusive Locks,即X锁)和共享锁(Share Locks,即S锁)。当数据对象被加上排它锁时,其他的事务不能对它读取和修改。加了共享锁的数据对象可以被其他事务读取,但不能修改。数据库利用这两 种基本的锁类型来对数据库的事务进行并发控制。

  3、死锁

    (1)死锁的第一种情况

一个用户A 访问表A(锁住了表A),然后又访问表B;另一个用户B 访问表B(锁住了表B),然后企图访问表A;这时用户A由于用户B已经锁住表B,它必须等待用户B释放表B才能继续,同样用户B要等用户A释放表A才能继续,这就死锁就产生了。 

    (2)死锁的第二种情况 
用户A查询一条纪录,然后修改该条纪录;这时用户B修改该条纪录,这时用户A的事务里锁的性质由查询的共享锁企图上升到独占锁,而用户B里的独占锁由于A 有共享锁存在所以必须等A释放掉共享锁,而A由于B的独占锁而无法上升的独占锁也就不可能释放共享锁,于是出现了死锁。这种死锁比较隐蔽,但在稍大点的项目中经常发生。如在某项目中,页面上的按钮点击后,没有使按钮立刻失效,使得用户会多次快速点击同一按钮,这样同一段代码对数据库同一条记录进行多次操作,很容易就出现这种死锁的情况。  

  4、死锁的产生4个必要条件:   

    1)互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放 
          2)占有且等待:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。 
          3)不可抢占条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用 
          4)循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。

   5、如何避免死锁

    (1)使用事务时,尽量缩短事务的逻辑处理过程,及早提交或回滚事务; 
    (2)设置死锁超时参数为合理范围,如:3分钟-10分种;超过时间,自动放弃本次操作,避免进程悬挂; 
    (3)所有的SP都要有错误处理(通过@error) 
    (4) 一般不要修改SQL SERVER事务的默认级别。不推荐强行加锁 
    (5)优化程序,检查并避免死锁现象出现; 

   6、锁模式

    (1)共享锁;

    (2)更新锁(U锁)

      两个步骤:

        2.1扫描获取Where条件时。这部分是一个更新查询,此时是一个更新锁。

        2.2如果将执行写入更新。此时该锁升级到排他锁。否则,该锁转变成共享锁。

          更新锁可以防止常见的死锁。

     (3)排他锁

二、事物的七种传播行为 

  1、REQUIRED :需要事物:如果当前存在事物,就沿用当前事物,否则就新建一个事物

@GetMapping("/REQUIRED")
@Transactional
public User REQUIRED(){
        User user = new User();
        user.setId(2);
        user.setName("andy");
        userService.insert(user);
        userService.insert(user);
        return userService.getById(2);
 }

  默认事务传播行为 REQUIRED ,如果有事务,那么加入事务,没有的话新创建一个

  结果: 创建事务,由于第二个插入语句失败,事务回滚,二个插入语句均失败。  

  2、SUPPORTS:支持事物:如果当前存在事物,就沿用当前事物,如果不存在,则继续用无事物的方式运行子方法

@GetMapping("/SUPPORTS")
@Transactional(propagation = Propagation.SUPPORTS)
public User SUPPORTS(){
    User user = new User();
    user.setId(3);
    user.setName("andy");
    userService.insert(user);
    user.setId(2);
    userService.insert(user);
    return userService.getById(2);
}

  事务传播行为 SUPPORTS 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行

  结果: 该controller 中的方法 SUPPORTS() 没有在其他事务方法中运行,所以会以非事物方法运行,所以3 插入成功,2 插入失败,如果 2 在 之前插入,那么 2,3 均  失败,因为 2 已经存在直接报RuntimeException

  3、MANDATORY:必须使用事物:如果当前没有事物,则抛出异常,如果存在当前事物,就沿用当前事物

@GetMapping("/MANDATORY")
@Transactional(propagation = Propagation.MANDATORY)
public User MANDATORY(){
    User user = new User();
    user.setId(2);
    user.setName("andy");
    userService.insert(user);
    user.setId(3);
    userService.insert(user);
    return userService.getById(3);
}

  事务传播行为 MANDATORY 必须在一个已有的事务中执行,否则抛出异常 (MANDATORY: 强制性的)

  4、REQUITES_NEW:创建新的事物:无论当前是否有事物,都会创建新的事物,这样新的事物就可以有新的锁和隔离级别等特性,与当前事物相互独立

@GetMapping("/REQUIRES_NEW")
@Transactional
public User REQUIRES_NEW(){
    User user = new User();
    user.setId(2);
    user.setName("andy");
    userService.insert2(user);
    user.setId(2);
    userService.insert(user);
    return userService.getById(2);
}

  事务传播行为 REQUIRES_NEW  不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行以前的事务

  5、NO_SUPPORTED:不支持事物,当前存在事物时,将事物挂起,运行方法

@GetMapping("/NOT_SUPPORTED")
@Transactional(propagation = Propagation.REQUIRED)
public User NOT_SUPPORTED(){
    User user = new User();
    user.setId(2);
    user.setName("andy");
    userService.insert2(user);
    user.setId(2);
    userService.insert2(user);
    return userService.getById(2);
}

  以非事务方式执行操作,如果当前存在事务,就把当前事务挂起

  6、NEVER:不支持事物:如果当前存在事物,则抛出易常,否则还有无事物执行

@GetMapping("/NEVER")
@Transactional
public User NEVER(){
    User user = new User();
    user.setId(2);
    user.setName("andy");
    userService.insert(user);
    user.setId(3);
    userService.insert(user);
    return userService.getById(3);
}

  事务传播行为 NEVER 不能在一个事务中执行,就是当前必须没有事务,否则抛出异常

  7、NESTED:在当前方法调用子方法时,如果子方法发生异常,只回滚子方法执行过的SQL,而不回滚当前的方法的事物。

@GetMapping("/NESTED")
@Transactional(propagation = Propagation.NESTED)
public User NESTED(){
   return null;
}

 

 

 

    

 

posted on 2019-11-05 10:06  婷好  阅读(405)  评论(0编辑  收藏  举报