微信号:java_bj

介绍:从算法基础到常用框架的知识体系,从初级程序员到高级架构师的成长之路,从创业小团队到Google、BAT的工作机会,始于JAVA而又不止于JAVA.JAVAer在北京,我们一起成长.

数据库系列二:事务(中)

2016-07-19 08:54 盛飞
点击上方“Java北京”关注我们


相关阅读:数据库系列一:事务(上)

六、事务与锁

事务隔离级别是通过锁的机制来实现的,锁是防止其他事务访问指定的资源,保证并发控制的手段,为了提高系统性能加快事务处理速度,应该使锁定的资源最小化。

(一)依据锁定方式,锁大概分为两类:排他锁(x锁)和共享锁(s锁)。

1.  排他锁——被加锁的对象只能被持有锁的事务读取和修改,其他事务不能在该对象上加其他锁,也不能读取和修改对象。

2.  共享锁——被加锁的对象可以被其他事务读取,但是不能被修改,其他事务也可以在上面加共享锁。

3.  意向锁——如果对一个节点加意向锁,说明该节点的下层节点正在被加锁;对任意节点加锁时,必须先对他的上层节点加意向锁。如:对表中的任一行加锁时,必须先对它所在的表加意向锁,然后再对该行加锁。这样一来,事务对表加锁时,就不再需要检查表中每行记录的锁标志位了,系统效率得以大大提高。

4.  更新锁——用于防止死锁,一个资源上只能有一个更新锁定,且只有获得更新锁定的事务才能获得排他锁定。

5.  模式锁——结构模式修改锁定(执行DDL时采用);结构描述固定锁定(编译查询时使用),不阻塞任何事务锁。

(二)依据锁定粒度,DML锁可分为两个层次:

1.  行级锁——TX锁/事务锁,可以对应多个被事务锁定的数据行。在Oracle每行数据上都有一个标志位来表示该行数据是否被锁定。

2.  表级锁——TM锁,有一个问题:如果给表加锁,除了要检查表原有的表级锁是否相容,还要检查表中每一行上的锁是否相容,从而引出了意图锁。 

注:

当前 MySQL 支持MyISAM、MEMORY(HEAP) 类型表的表级锁, BDB 表支持页级锁,InnoDB、Archive 表支持行级锁。

表级锁的优点及选择:

1)很多操作都是读表。

2)在严格条件的索引上读取和更新,当更新或者删除可以用单独的索引来读取得到时: UPDATE tbl_name SET column=value WHERE unique_key_col=key_value;

DELETEFROM tbl_name WHERE unique_key_col=key_value;

3) SELECT 和 INSERT 语句并发的执行,但是只有很少的 UPDATE 和 DELETE 语句。

4)很多的扫描表和对全表的 GROUP BY 操作,但是没有任何写表。

表锁的缺点:

1)一个客户端提交了一个需要长时间运行的SELECT 操作。

2)其他客户端对同一个表提交了 UPDATE 操作,这个客户端就要等到 SELECT 完成了才能开始执行。

3)其他客户端也对同一个表提交了 SELECT 请求。由于 UPDATE 的优先级高于 SELECT ,所以 SELECT 就会先等到 UPDATE 完成了之后才开始执行,它也在等待第一个 SELECT 操作。

行级锁的优点及选择:

1)在很多线程请求不同记录时减少冲突锁。

2)事务回滚时减少改变数据。

3)使长时间对单独的一行记录加锁成为可能。

行级锁的缺点:

1)比页级锁和表级锁消耗更多的内存。

2)当在大量表中使用时,比页级锁和表级锁更慢,因为他需要请求更多的资源。

3)当需要频繁对大部分数据做 GROUP BY 操作或者需要频繁扫描整个表时,就明显的比其它锁更糟糕。

4)使用更高层的锁的话,就能更方便的支持各种不同的类型应用程序,因为这种锁的开销比行级锁小多了。

5)可以用应用程序级锁来代替行级锁,例如 MySQL 中的 GET_LOCK() 和 RELEASE_LOCK() 。但它们是劝告锁(原文: These areadvisory locks),因此只能用于安全可信的应用程序中。

6)对于 InnoDB 和 BDB 表,MySQL 只有在指定用 LOCK TABLES 锁表时才使用表级锁。在这两种表中,建议最好不要使用 LOCK TABLES ,因为 InnoDB 自动采用行级锁, BDB 用页级锁来保证事务的隔离。

(三)封锁协议保证如何防止事务问题:

1.  一级封锁协议:事务T在修改数据R之前必须先对其加X锁,直到事务结束才释放。一级封锁协议可以防止丢失更新,并保证事务T是可恢复的。但是不能防止脏读、不可重复的读及幻象。

2.  二级封锁协议:一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,读完后立即释放S锁。二级封锁协议可以防止丢失更新、脏读,但是不能防止不可重复的读和幻象。

3.  三级封锁协议:一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,直到事务结束才释放S锁。三级封锁协议可以防止丢失更新、脏读、不可重复的读和幻象。

(四)锁带来的问题

通过锁可以实现事务的隔离性,使得事务可以并发工作,但是却会带来问题:

1、脏读——事务隔离级别-Read uncommitted
脏读即一个事务可以读到另一个事务中未提交的数据

2、不可重复读
事务(A)读取到了另一个事务(B)已经提交的更改数据
解决方法:将事务隔离级别调整为RC

3、丢失更新:没有事务隔离

什么时候出现丢失更新:两个事务同时更新一条数据
解决方法:让事务变成串行操作,而不是并发的操作,即对每个事务开始---对读取记录加排他锁

4、泛读
事务(A)读取到了另一个事务(B)提交的新增数据
解决方法:使用Next-KeyLock算法来避免不可重复读的问题。

七、事务类型

按照作用范围可分为本地事务和分布式事务(全局事务或者XA事务)。

本地事务

完全依赖于DB/JMS等资源本身,如直接调用JDBC中的conn.commit()/conn.rollback(),这里跟应用服务器没有关系,所以不支持多数据源,针对数据库又称为JDBC事务。

分布式事务

一个分布式事务(Distributed Transaction)包括一个事务管理器(Transaction Manager)和一个或者多个资源管理器(ResourceManager)。一个资源管理器是任意类型的持久化的数据存储(比如数据库,JMS等等)。

X/Open 组织(即现在的OpenGroup)定义了分布式事务处理模型。 X/Open DTP (Distributed Transaction Processing)模型包括应用程序(AP)、事务管理器(TM)、资源管理器(RM)、通信资源管理器(CRM)四部分。一般,常见的事务管理器(TM)是交易中间件,常见的资源管理器(RM)是数据库,常见的通信资源管理器(CRM)是消息中间件。事务管理器承担着所有事务参与方的相互通信的责任。

JAVA事务API(JTA: Java Transaction API)和JAVA事务服务(JTS: Java Transaction Service)为J2EE平台提供了分布式事务服务。JTA是用户编程接口,JTS是服务器底层服务,两者一般由应用服务器自带实现。

八、    Spring事务传播机制(Transaction Attributes)

Spring在TransactionDefinition接口中规定了7种类型的事务传播行为,它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播。

事务属性可以指定到Bean也可以指定到具体方法,方法级别的属性设置覆盖Bean级别的属性设置。EntityBean只能使用Required/RequiresNew/Mandatory;MDB只能使用Required/NotSupported;如果EJB实现了synchronization接口只能使用Required/ RequiresNew/Mandatory,此接口提供了afterBegin()/beforeCompletion() /afterCompletion()三个回调方法,这三个方法可以独立于事务管理处理一些事务前后的事件,比如Hibernate就使用此接口与JTA事务管理集成,在afterCompletion()方法中调用session.afterTransactionCompletion()方法来释放一级和二级缓存锁。

1. Required:如果Context中有事务就加入,没有就自己创建一个。

2. Mandatory:永远加入一个事务。如果当前Context没有事务,抛出TransactionRequiredException异常,也就是要求方法启动之前有事务上下文的传递。

3. RequiresNew:永远新建一个事务,暂停原来的事务,执行完以后,激活原来的事务,这大多时候是违反ACID原则的。但是有些场景可能比较有用,就是不管其他操作是成功或者失败,必须执行的任务,(那些不管别人如何,自己必须提交事务的方法,比如审计日志是一定要写的)

4. Supports:如果有事务就加入,如果没有就算了。永远不会创建新事务。(一般用于只读方法,不会主动创建事务,但如果当前有事务就加入,以读到事务中未提交的数据)。

示例场景(商城买家每天的交易额限制为不能超过1000元)。

使用SUPPORTS属性:

    Step1:查询当天累积交易额为900元

    Step2:开始事务

    Step3:发起新交易为200元额度

    Step4:在Supports属性下查询得到累积交易额为900+200=1100元

    Step5:抛出异常,提示超出额度,回滚事务

使用NotSupported属性:

    Step1:查询当天累积交易额为900元

    Step2:开始事务

    Step3:发起新交易为200元额度

    Step4:在NotSupported属性下查询得到累积交易额为900元

    Step5:业务正常,事务提交

5. NotSupported:永远不使用事务,如果当前有事务,挂起事务。(那些有可能抛异常但异常并不影响全局的方法)

6. Never:不能在有当前事务的情况下调用本方法,如果有事务上下文会抛出异常。

7. NESTED:嵌套事务并且使用Required属性。


相关阅读:数据库系列一:事务(上)


是时候关注一个只分享干货的公众号了

长按二维码 关注我们

JAVA北京(java_bj)


 
Java北京 更多文章 Java中的ThreadPoolExecutor类 Innodb与MyISAM的区别 缓存架构设计细节二三事 线程数究竟设多少合理 如何选择开源项目?
猜您喜欢 jsp中的JSTL与EL表达式用法及区别(一) 浅谈大型网站动态应用系统架构 生活和工作需要小结 8张图理解Java UPYUN 荣获第六届互联网行业牛耳奖“最佳创新企业”