事务实战感悟

在JavaEE的三层架构体系(UI层、业务层、数据访问层次)中,事务体现在数据访问层。本文试图从分布式、Spring、Mybatis、JDBC、数据库、锁六个角度来看写。数据库为MySql,存储引擎为Innodb。

从数据库看事务:

事务的四个特性:

Automicity(原子性)、Consistency(一致性)、Isolation(隔离性)、Durability(持久性)。

通俗的讲:就是一个或一组SQL语句组成的单元(原子性)在执行的时候,和其它正在执行的SQL语句是无关的(隔离性),并且多个用户查询的执行结果是一样的(一致性),这种结果是永久的,即使数据库崩溃或者数据存储介质被破坏,系统也能恢复到最后一次成功执行的结果(永久性)。

事务的生命周期:

一般的,事务都是自动提交(注意上面已经提到是MySql数据库存储引擎为Innodb)。关闭自动提交语句:SET AUTOCOMMIT=0(慎用)。或是手动开始一个事务需要使用命令:BEGIN或START TRANSACTION。注意的是BEGIN或START TRANSACTION会禁用自动提交,直到使用COMMIT或ROLLBACK结束事务为止。

另外DDL(数据定义语言)、DCL(数据控制语言)的事务是隐性提交的,相当于在执行此类语句前,已经进行了一个COMMIT。

事务的隔离级别

关于事务的隔离性,在并发操作中可能出现三种问题:脏读、不可重复读、幻读。

脏读:一个事务读取了另一个事务未提交的数据。

不可重复读:一个事务的两次读取结果不一致(被update)。

幻读:一个事务的两次读取结果不一致(被insert或delete)。

数据库提供了四种隔离级别:

Read uncommitted(读未提交)、Read committed(读已提交)、Repeated read(可重复读)、Serializable(串行化)。隔离级别依次升高,执行效率依次降低。

下图展示的四种隔离级别对三种问题的解决情况。其中绿色字体为默认的隔离级别:

从锁看事务:

表级锁、页级锁、行级锁

我们已经知道了数据库的事务,而数据库的事务实际上是通过锁来实现的。从锁定的范围来讲一般都三种锁:表级锁、页级锁、行级锁。表现如下:

个人理解mysql是提倡行级锁的,而行级锁的实现很大程度依赖于索引。当操作无法利用索引时,Innodb会放弃使用行级锁而改用表级锁。需要特别说明的是主键是自带索引属性的。

共享锁(S)、排他锁(X)、意向共享锁(IS)和意向排他锁(IX)

从锁定模式的分类来讲有四种:共享锁(S)、排他锁(X)、意向共享锁(IS)和意向排他锁(IX)。表现如下:

 

共享锁(S)

排他锁(X)

意向共享锁(IS)

意向排他锁(IX)

共享锁(S)

兼容

冲突

兼容

冲突

排他锁(X)

冲突

冲突

冲突

冲突

意向共享锁(IS)

兼容

冲突

兼容

兼容

意向排他锁(IX)

冲突

冲突

兼容

兼容

乐观所和悲观锁

在并发访问数据库的时候,有两种技术方案:悲观锁(悲观并发控制),乐观锁(乐观并发控制)。

悲观锁就是使用数据库提供的锁机制。另外可以发现,在使用了悲观锁的情况下,发生了锁表依然可以查询出数据,这是因为MySql采用了多版本并发控制机制(MVCC)。

乐观锁不会使用数据库提供的锁机制。而是采用记录数据版本的方式。实现数据版本有两种办法:第一是使用版本号;第二是使用时间戳。

从JDBC看事务

jdbc开发步骤:注册驱动、获取链接、创建对象、执行SQL。如果完全的按照以上的开发步骤,不做特别设置。

默认的:

事务的提交方式使用的则是自动提交。

事务的隔离级别则是可重复读。

并且用的是悲观锁机制。

如果要修改为手动提交:con.setAutoCommit(false);

如果要修改事务的隔离级别:

con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);

其中,JDBC定义了五种事务隔离级别。

TRANSACTION_NONE                              驱动不支持事务

TRANSACTION_READ_UNCOMMITTED       允许脏读、不可重复读和幻读

TRANSACITON_READ_COMMITTED                允许不可重复读和幻读;不支持脏读

TRANSACTION_REPEATABLE_READ                不允许脏读、不可重复读和幻读

TRANSACTION_SERIALIZABLE                 不允许脏读、不可重复读和幻

如果要使用乐观锁,则需要自己进行实现。

从Mybatis看事务

Mybatis是支持定制化SQL、以及高级映射的持久层框架。其中,Mybatis中提供的JdbcTransaction和纯粹的Jdbc事务几乎没有差距,只能说仅仅扩张了支持连接池的connection。

另外,Mybatis提供的ManagedTransaction(托管事务)仅是提醒用户,在其它环境中把事务托管给其它框架,比如托管给Spring。

从Spring看事务

Spring事务的本质可以理解为就是对数据库事务的支持,但是Spring让事务管理变得有效和简单。

Spring支持事务的隔离级别(isolation=)。

Spring定义了自己的事务传播属性(propagation=)。即同时存在多个事务时,Spring应该如何处理这些事务的行为。见下表:

PROPAGATION_REQUIRED

支持当前事务。如果当前没有事务,就新建一个事务(默认)

PROPAGATION_NESTED

支持当前事务。如果当前没有事务,就新建一个事务。内部事务回滚不会对外部事务造成影响。

PROPAGATION_SUPPORTS

支持当前事务。如果当前没有事务,就以非事务方式运行

PROPAGATION_MANDATORY

支持当前事务。如果当前没有事务,就抛出异常。

PROPAGATION_NOT_SUPPORTED

以非事务方式运行。如果当前存在事务,就把当前事务挂起。

PROPAGATION_NEVER

以非事务方式运行。如果当前存在事务,就抛出异常。

PROPAGATION_REQUIRES_NEW

新建事务。如果当前已有事务,就把当前事务挂起。

Spring支持编程式事务管理和声明式事务管理两种方式。编程式事务允许在代码中精确定义事务的边界,而声明式事务(基于AOP)有助于将操作与事务规则进行解耦。推荐使用声明式事务注解方式。

原文地址:https://www.cnblogs.com/shuaixianbohou/p/7020298.html