在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)有助于将操作与事务规则进行解耦。推荐使用声明式事务注解方式。