2018-03-21

[toc]

事务介绍

四大特性ACID

1. 原子性(Atomicity)

2. 一致性(Consistency) //真正的老大

3. 隔离性(Isolation)

4. 持久性(Durability)

高并发的情况下,多个事务之间会出现问题:

1. 脏读(Dirty Read):事务A读取了事务B未提交的数据,并在这个基础上又做了其它操作。

2. 不可重复读(Unrepeatable Read): 事务A读取了事务B已提交的更改数据。

3. 幻读(Plantom Read)::事务A读取了事务B已提交的新增数据。

事务隔离级别

为了应对这些问题,隔离不同的事务,出现了事务隔离级别

1. READ_UNCOMMITTED

2. READ_COMMITTED

3. REPEATABLE_READ

4. SERIALIZABLE

从上往下,级别越来越高,安全性能越来越高,并发性能越来越差。

|事务隔离级别|脏读|不可重复读|幻读|

|---------|--|---|

|READ_UNCOMMITTED|允许|允许|允许|

|READ_COMMITTED|禁止|允许|允许|

|REPEATABLE_READ|禁止|禁止|允许|

|SERIALIZABLE|禁止|禁止|禁止|

JDBC也提供了这四类事务的隔离级别,默认的隔离级别对不同的数据库产品而言不一样。MySQL数据库默认隔离级别READ_COMMITTED


`DatabaseMetaData meta = DBUtil.getDataSource().getConnection().getMetaData();`

`int` `defaultIsolation = meta.getDefaultTransactionIsolation();`

java.sql.Connection类中可以查看所有的隔离级别。

Spring中的事务

事务的传播行为

  1. ** PROPAGATION_REQUIRED**
  2. ** RROPAGATION_REQUIRES_NEW**
  3. ** PROPAGATION_NESTED**
  4. ** PROPAGATION_SUPPORTS**
  5. ** PROPAGATION_NOT_SUPPORTED**
  6. ** PROPAGATION_NEVER**
  7. ** PROPAGATION_MANDATORY**

假设事务从方法 A 传播到方法 B,您需要面对方法 B,问自己一个问题:

方法 A 有事务吗?

1. 如果没有,就新建一个事务;如果有,就加入当前事务。这就是 PROPAGATION_REQUIRED,它也是 Spring 提供的默认事务传播行为,适合绝大多数情况。

2. 如果没有,就新建一个事务;如果有,就将当前事务挂起。这就是 RROPAGATION_REQUIRES_NEW,意思就是创建了一个新事务,它和原来的事务没有任何关系了。

  1. 如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。这就是 PROPAGATION_NESTED,也就是传说中的“嵌套事务”了,所嵌套的子事务与主事务之间是有关联的(当主事务提交或回滚,子事务也会提交或回滚)。

4. 如果没有,就以非事务方式执行;如果有,就使用当前事务。这就是 PROPAGATION_SUPPORTS,这种方式非常随意,没有就没有,有就有,有点无所谓的态度,反正我是支持你的。

5. 如果没有,就以非事务方式执行;如果有,就将当前事务挂起。这就是 PROPAGATION_NOT_SUPPORTED,这种方式非常强硬,没有就没有,有我也不支持你,把你挂起来,不鸟你。

6. 如果没有,就以非事务方式执行;如果有,就抛出异常。这就是 PROPAGATION_NEVER,这种方式更猛,没有就没有,有了反而报错,确实够牛的,它说:我从不支持事务!

7. 如果没有,就抛出异常;如果有,就使用当前事务。这就是 PROPAGATION_MANDATORY,这种方式可以说是牛逼中的牛逼了,没有事务直接就报错,确实够狠的,它说:我必须要有事务!

  1. <small>来源: http://www.importnew.com/21603.html#comment-514576</small>

spring又提供了其它的方法:

  • ****事务超时(Transaction Timeout)****:为了解决事务时间太长,消耗太多的资源,所以故意给事务设置一个最大时常,如果超过了,就回滚事务。

  • ****只读事务(Readonly Transaction)****:为了忽略那些不需要事务的方法,比如读取数据,这样可以有效地提高一些性能。

spring声明式事务

基于TransactionProxyFactoryBean的方式

这种方式并不经常使用,需要对每个类都需要配置代理类,维护开发比较麻烦。

1 配置文件中配置事务管理器


<!-- 配置事务管理器  -->

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

    <!-- 数据源 -->

    <property name="dataSource" ref="dataSource"/>

</bean>    

2 配置业务层代码


<!-- 配置业务层的代码 -->

    <bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

        <!--  配置目标对象-->

        <property name="target" ref="accountService"/>

        <!-- 注入事务管理器 -->

        <property name="transactionManager" ref="transactionManager"/>

        <!--  注入事务属性-->

        <property name="transactionAttributes">

            <props>

                <prop key="transfer">PROPAGATION_REQUIRED </prop><!-- 逗号隔开后面的属性 -->

            </props>

        </property>

    </bean>      

3. 注入代理类: "accountServiceProxy"

基于AspectJ的xml配置方式。

应用较广泛

1 配置事务管理器


    <!-- 配置事务管理器  -->

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

        <!-- 数据源 -->

        <property name="dataSource" ref="dataSource"/>

    </bean>  

2 配置事务的通知


<!-- 配置事务的通知:(事务的增强) -->

    <tx:advice id="txAdvice" transaction-manager="transactionManager">

        <tx:attributes>

            <tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/>

        </tx:attributes>

    </tx:advice>  

3 配置切面


<!-- 配置切面 -->

    <aop:config>

        <!-- 配置切入点 -->

        <aop:pointcut expression="execution(* com.spring.demo3.AccountService+.*(..))" id="pointcut1"/>

        <!-- 配置切面 -->

        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>

    </aop:config>

基于注解的事务管理

1 配置事务管理器


<!-- 配置事务管理器  -->

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

        <!-- 数据源 -->

        <property name="dataSource" ref="dataSource"/>

    </bean>    

2 开启注解事务


    <!-- 开启注解事务 -->

    <tx:annotation-driven  transaction-manager="transactionManager"/>    

3 在业务层上添加注解


@Transactional(propagation=Propagation.REQUIRED)    

分布式事务

分布式事务的原因:

1. 分库,分表。当数据库单表一年产生的数据超过1000W,那么就要考虑分库分表,具体分库分表的原理在此不做解释,以后有空详细说,简单的说就是原来的一个数据库变成了多个数据库。这时候,如果一个操作既访问01库,又访问02库,而且要保证数据的一致性,那么就要用到分布式事务

2. 应用的SOA化。 所谓的SOA化,就是业务的服务化。比如原来单机支撑了整个电商网站,现在对整个网站进行拆解,分离出了订单中心、用户中心、库存中心。对于订单中心,有专门的数据库存储订单信息,用户中心也有专门的数据库存储用户信息,库存中心也会有专门的数据库存储库存信息。这时候如果要同时对订单和库存进行操作,那么就会涉及到订单数据库和库存数据库,为了保证数据一致性,就需要用到分布式事务。

本质上来说,分布式事务就是为了保证不同数据库的数据一致性。

分布式事务的应用场景

1. 支付

2. 在线下单

常见的分布式事务解决方案

基于XA协议的两阶段提交

XA是一个分布式事务协议,由Tuxedo提出。XA中大致分为两部分:事务管理器和本地资源管理器。

其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。总的来说,XA协议比较简单,而且一旦商业数据库实现了XA协议,使用分布式事务的成本也比较低。但是,XA也有致命的缺点,那就是性能不理想,特别是在交易下单链路,往往并发量很高,XA无法满足高并发场景。XA目前在商业数据库支持的比较理想,在mysql数据库中支持的不太理想,mysql的XA实现,没有记录prepare阶段日志,主备切换回导致主库与备库数据不一致。许多nosql也没有支持XA,这让XA的应用场景变得非常狭隘。

消息事务+最终一致性

所谓的消息事务就是基于消息中间件的两阶段提交,本质上是对消息中间件的一种特殊利用,它是将本地事务和发消息放在了一个分布式事务里,保证要么本地操作成功成功并且对外发消息成功,要么两者都失败,开源的RocketMQ就支持这一特性,具体原理如下:

1. A系统向消息中间件发送一条预备消息
2. 消息中间件保存预备消息并返回成功
3. A执行本地事务
4. A发送提交消息给消息中间件

通过以上4步完成了一个消息事务。对于以上的4个步骤,每个步骤都可能产生错误,下面一一分析:

  • 步骤一出错,则整个事务失败,不会执行A的本地操作

  • 步骤二出错,则整个事务失败,不会执行A的本地操作

  • 步骤三出错,这时候需要回滚预备消息,怎么回滚?答案是A系统实现一个消息中间件的回调接口,消息中间件会去不断执行回调接口,检查A事务执行是否执行成功,如果失败则回滚预备消息

  • 步骤四出错,这时候A的本地事务是成功的,那么消息中间件要回滚A吗?答案是不需要,其实通过回调接口,消息中间件能够检查到A执行成功了,这时候其实不需要A发提交消息了,消息中间件可以自己对消息进行提交,从而完成整个消息事务

    <small></small>

    <small>基于消息中间件的两阶段提交往往用在高并发场景下,将一个分布式事务拆成一个消息事务(A系统的本地操作+发消息)+B系统的本地操作,其中B系统的操作由消息驱动,只要消息事务成功,那么A操作一定成功,消息也一定发出来了,这时候B会收到消息去执行本地操作,如果本地操作失败,消息会重投,直到B操作成功,这样就变相地实现了A与B的分布式事务。原理如下:</small>

    image.png
虽然上面的方案能够完成A和B的操作,但是A和B并不是严格一致的,而是最终一致的,我们在这里牺牲了一致性,换来了性能的大幅度提升。当然,这种玩法也是有风险的,如果B一直执行不成功,那么一致性会被破坏,具体要不要玩,还是得看业务能够承担多少风险。

TCC编程模式

所谓的TCC编程模式,也是两阶段提交的一个变种。TCC提供了一个编程框架,将整个业务逻辑分为三块:Try、Confirm和Cancel三个操作。以在线下单为例,Try阶段会去扣库存,Confirm阶段则是去更新订单状态,如果更新订单失败,则进入Cancel阶段,会去恢复库存。总之,TCC就是通过代码人为实现了两阶段提交,不同的业务场景所写的代码都不一样,复杂度也不一样,因此,这种模式并不能很好地被复用。

image.png

分布式事务

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容