• 主题:数据库事务与并发(Hibernate)


    数据库事务中的基本概念

    数据库事务是指,由一个或多个SQL语句组成的工作单元,这个工作单元中的SQL语句相互依赖,如果有一个SQL语句失败,那么整个操作都要撤销。在并发环境中,当多个事务同时访问同一资源时,可能会造成并发问题,此时可以使用数据库系统的事务隔离级别来避免各类并发问题。此外,在应用程序中还可以使用悲观锁和乐观锁来解决丢失更新的并发问题。

    数据库事务必须具备ACID的特征(Atomic原子性,Consistency一致性,Isolation隔离性,Durability持久性)
        原子性,指整个数据库事务是不可分割的单元。只有所有的语句执行成功,才算成功。
        一致性,指数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。
         隔离性,指在并发环境中,当不同的事务同时操作相同的数据时,每个事务都有各自的完整数据空间。
         持久性,指的是只要事务成功结束,对数据库做的更新要永久保存下来.

    Transaction和Session的关系,应该注意以下几点
          Transaction的rollback()和Session的close()方法都会抛出HibernateException
         不论事务是否成功,最后都应该关闭Session。
          即使事务中只包含只读操作,也应该在事务成功执行之后提交事务,并且在事务执行失败时撤销事务,因为在提交或撤销事务时,数据库系统会释放占用的资源。

    多个事务同时运行时的并发问题
         第一类丢失更新,撤销一个事务时,把其他事务已提交的更新数据覆盖了。
         脏读,一个事务读到另一个事务未提交的更新数据。
         虚读,一个事务读到另一个事务已提交的新插入的数据。
         不可重复读,一个事务读到另一个事务已提交的更新数据。
          第二类更新,一个事务覆盖另一事务已提交的更新数据。

    数据库系统锁的基本原理

    按照锁定的资源的粒度,锁可以分为以下类型
       数据库级锁: 锁定整个数据库
       表级锁: 锁定一张表
       区域级锁: 锁定数据库的特定区域
       页面级锁: 锁定数据库的特定页面
       见面级锁: 锁定数据库的特定页面
       键值级锁: 锁定数据库表中带有索引的一行数据。
       行级锁: 锁定数据库表中的但行数据(即一条记录)


    按照封锁的程度,锁可以分为: 共享锁,独占锁,更新锁

    共享锁, 共享锁用于读数据操作,它是非独占的,允许其他事务同时读取其锁定的资源,但不允许其他事务更新数据。共享锁的特征:
    加锁条件:当一个事务执行Select操作时,数据库系统就会为其分配一把共享锁,来锁定查询数据。
         解锁条件:在默认情况下,数据读取完毕,共享锁就解除了。
         与其他锁的兼容性:如果数据资源上放置了共享锁,还能再放置共享锁和更新锁。
         并发性能:具有良好的并发性能,当多个事务读相同的数据时,每个事务都会获得共享锁,因此可以同时读锁定的数据。

    独占锁,也叫排它锁,适用于修改数据的场合。它锁定的资源,其他事务不能读取也不能更新。独占锁具有以下特征:
        加锁的条件:当一个事务执行insert、update、或delete语句时,数据库就会为SQL所操作的数据使用独占锁。如果数据上有其他锁,那么就能放置独占锁.
        解锁条件:到事务结束时才能被解除
    与其他锁的兼容性:独占锁不能和其他锁兼容。通用如果资源上有其他锁,那么也不能放置独占锁。
        并发性能:性能较差,只允许有一个事务访问锁定的数据,如果其他事务也需要访问该数据,就必须等待,直到前一个事务结束,解除了独占锁,其他事务才有机会访问资源。

    更新锁,在更新操作的初始化阶段用来锁定可能要被修改的资源,这可以避免使用共享锁造成死锁的现象。更新具有以下特征:
        加锁的条件:当一个事务执行update语句时,数据库系统会先为事务分配一把更新锁。
        解锁条件:读取数据完毕,执行更新操作时,会把更新锁升级为独占锁。
         与其他锁的兼容性:更新锁与共享锁是兼容的。也就是说,一个资源可以同时放置更新锁和共享锁,但是最多只能放置一把更新锁。
        并发性能:允许多个事务同时访问资源。但不允许修改。

    死锁及其防止方法
         合理安排表的访问顺序
         使用短事务。将大事务分割为多个小事务执行。应该在处理事务之前就准备好用户必须提供的数据。
         如果对数据的一致性要求不高,可以允许脏度。
         如果可能,错开多个事务访问相同数据资源的时间,以防止锁冲突。
         使用尽可能低的事务级别。





    数据库的隔离级别
         尽管数据库系统允许用户在事务中显示的为数据资源加锁,但是首先应该考虑让数据库系统自动管理锁,它会分析事务中的SQL语句,然后自动为SQL语句所操作的数据资源加上合适的锁,而且在锁的数目太多时,数据库系统会自动的进行锁升级,以提供系统性能。

    数据库系统提供了四种事务隔离级别供用户选择:

    Serializable,串行化。
          一个事务在执行过程中完全看不到事务对数据库所做的更新。当两个事务同时操作数据库中的数据时,如果第一个事务已经在访问该数据,那么第二个事务只能停下来等待。
    Repeatable Read:可重复读
          一个事务在执行过程中可以看到其他事务已经提交的新插入的数据。但是不能看到其他事务对已有数据做的跟新。
    Read Commited:读已提交数据
          一个事务在执行过程中可以看到其他事务已经提交的新插入的数据。可以看到其他对已有数据进行的更新。
    Read UnCommited:读取未提交
         一个事务可以看到,其他事务没有提交新插入的数据。而且更新操作的记录也能看到。

    //: 在hibernate.cfg.xml中配置
    hibernate.connection.isolation=2

    在应用程序中才用悲观锁和乐观锁

    悲观锁与乐观锁的概念
    悲观锁,在应用程序中显示的为数据资源加锁。可以防止丢失更新和不可重复读问题。但是会影响性能。
    乐观锁,假设当前的事务操作的数据,不会有其他事务同时访问该数据资源,完全依靠数据库系统自动管理锁。因此可能会出现并发问题。

    利用数据库系统的独占锁来实现悲观锁
    悲观锁的两种实现方式
    应用中显示的指定采用数据库系统的独占锁来锁定数据资源。
    在数据库表中增加一个表明记录状态的LOCK字段,当它取值为Y时,表示该i记录已被某个事务锁定。如果为N,表示该条数据为空闲状态。
    在Hibernate中,当通过Session的get()和load()方式来加载一个对象时,可以采用以下方式使用悲观锁:
    Customer cus = (Customer)session.get(Customer.class, "c001", LockMode.UPGRADE); //: final org.hibernate.LockMode






    LockMode类表示的几种锁定模式

    锁定模式 描述
    LockMode.NONE 如果在Hibernate缓存中存在Customer对象,就直接返回该引用。否则通过select加载。默认值
    LockMode.READ 不管缓存中是否有,都使用select加载数据。如果映射文件设置了版本元素,就执行版本检查,比较缓存中的与取到的是否版本一致。
    LockMode.UPGRADE 不管缓存中是否有,都使用select加载数据。就执行版本检查,比较缓存中的与取到的是否版本一致。如果数据库系统支持悲观锁,那么执行select…for update, 否则执行简单的select
    LockMode.UPGRADE_NOWAIT 会执行LockMode.UPGRADE和它一样的功能。若果执行的select不能立即获得悲观锁,那么就会抛出异常
    LockMode.WRITE 当Hibernate保存或更新对象时,会自动使用这种锁定模式。这种锁定模式只在Hibernate内部使用,所以在应用中不应该使用它


    利用Hibernate的版本控制来实现乐观锁
    create table Accounts (
    id bigint primary key,
    name varchar(15),
    balance numeric(10,2),
    LAST_UPDATED_TIME timestamp,
    version int
    )

    在hbm中配置版本控制
    //: 在元素必须跟在<id>元素的后面
    <version name = "version" column = "VERSION" />

    //: 使用该元素也可以实现版本控制
    <timestamp name=”lastUpdateTime” column=” LAST_UPDATED_TIME” />

    对游离对象进行版本检查
    Session session1 = ....;
    trans1 = session1.beginTransaction();
    Account a = (Account)session1.get(Account.class, new Long(1));
    trans1.commit();
    session1.close();

    a.setBalance(a.getBalance()-100);

    Session session2 = ...;
    trans2 = session2.beginTransaction();
    session2.lock(a, LockMode.READ);
    trans2.commit();
    session2.close();

    lock() 方法和update()方法之间的区别
    lock()方法在LockMode.READ模式下,立即进行版本控制。而update()方法不会立即执行版本检查,只有当Session清理缓存时,真正执行update时才进行版本检查
    lock()在默认的LockMode中不会执行update语句。而update()会计划执行一个update语句,如果数据库中没对应的记录那么会抛出异常。

    实现乐观锁的其他方式
    <class optimistic-lock="all" dynamic-update="true">

    //: optimistic-lock=all/dirty 时必须设置dynamic-update为true
  • 相关阅读:
    为sublime text2 添加SASS语法高亮
    下拉框点链接js
    [JavaScript] 初中级Javascript程序员必修学习目录
    判断页数及切换
    切换加上延迟加载js代码
    jquery 简单弹出层(转)
    左右滑动删除ListView条目Item--第三方开源--SwipeToDismiss
    使用自定义的item、Adapter和AsyncTask、第三方开源框架PullToRefresh联合使用实现自定义的下拉列表(从网络加载图片显示在item中的ImageView)
    从一个URL下载原始数据,基于byte字节,得到byte数组
    动画气泡指示当前滑动值--第三方开源--DiscreteSeekbar
  • 原文地址:https://www.cnblogs.com/aaa6818162/p/1535662.html
Copyright © 2020-2023  润新知