一、事务
事务是访问数据库的一个操作序列,数据库应用系统通过事务集来完成对数据库的存取。事物的正确执行是的数据库从一个状态转换成另一个状态。
事务必须服从ISO/IEC所指定的ACID原则。
- 原子性(atomicity)。即不可分割性,事务要么全部被执行,要么就全部不被执行。如果事务的所有子事务全部被提交成功(所有的数据库操作被提交),则数据库的状态发生转换;如果子事务执行失败,则其他的子事务的数据库操作被回滚,即数据库回到事务执行的状态之前,不会发生状态转换。
- 一致性或可串性(consistency)。事务的执行使得数据库从一种正确状态转换成另一种正确状态。 如果事务启动时数据是一致的,那么当这个事物成功后数据库的数据也必须是一致的。
- 隔离性(isolation)。在事务提交之前,不允许把该事务对数据的任何改变提供给任何其他事务。
- 持久性(durability)。事务正确提交后,其结果将永久保存在数据库中,即使在事务提交后有了其他故障,事务的处理结果也会得到保存。
- 注意:银行存取款中, 事务的执行过程中,必须允许暂时的不一致性,因为无论是A账户取出操作在前还是B账户存入操作在前,这两个操作必然有一个先后顺序,两个操作之间就会产生不一致。于是乎,在原子性和一致性的双重作用之下,事务就能够正确、有效地执行,实现响应的逻辑功能。
所以说原子性和一致性是有区别的,但是两者又是互补的,不能隔离开来。
- COMMIT 和 ROLLBACK 在大多数情况下,通过执行COMMIT或者ROLLBACK语句来终止事务。 当执行COMMIT语句时,自从事务启动以来对数据库所做的一切操作就会成为永久--即所有的改变被写入磁盘。 当执行ROLLBACK语句时,自从事务启动以来对数据库所做的一切操作都被撤销,且数据库返回到事务开始之前所处的状态。 不管哪种情况,数据库在事务完成时都保证能回到一致状态。
二、数据系统的两种事务模式
- 自动提交模式:每个SQL语句都是一个独立的事务,当数据库系统执行完一个SQL语句后,会自动提交事务。
- 手动提交模式:必须由数据库客户端程序显示指定事务开始边界和结束边界。
MySQL中数据库表常用的分为3种类型:INNODB,BDB和MyISAM,INNODB和BDB属于事务安全类表。
三、隔离级别
对于访问数据库相同数据的多个事务,如果没有采取必要的隔离机制,就会导致并发问题,这些并发问题可归纳为以下几类
- 更新遗失:两个事务都同时更新一行数据,一个事务对数据的更新把另一个事务对数据的更新覆盖了。这是因为系统没有执行任何的锁操作,因此并发事务并没有被隔离开来。
- 脏读:一个事务读取到另一个事务未提交的更新数据。因为没有最终提交,有可能是不正确的数据。
- 不可重复读:一个事务读到另一个事务已提交的更新数据。另一个事务提交前后的数据不一致。
- 第二类丢失更新:这是不可重复读中的特例,一个事务覆盖另一个事务已提交的更新数据。
- 幻读:一个事务读到另一个事务已提交的新插入的数据(事务1读取记录时事务2增加了记录并提交,事务1再次读取时可以看到事务2新增的记录;)。
隔离机制(从高到低)如下:
- 可串行化(Serializable)读加共享锁,写加排它锁。 读取事务可以并发,写事务之间是互斥的,必须一个一个的执行事务。
- 可重复读(Repeatable Read)读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。避免了不可重复读取和脏读,但是有时可能出现幻读。这可以通过“共享读锁”和“排他写锁”实现。
- 读已提交数据(Read Committed)读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。该隔离级别避免了脏读,但是却可能出现不可重复读。事务A事先读取了数据,事务B紧接了更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。
- 读未提交数据(Read Uncommitted)如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。避免了更新丢失,却可能出现脏读。也就是说事务B读取到了事务A未提交的数据。
隔离机制对应产生的问题(Y引起问题):
隔离机制/并发问题 | LU丢失更新 | DR脏读 | NRR非重复读 | SLU二类丢失更新 | PR幻读 |
未提交读(Read Uncomitted) | N | Y | Y | Y | Y |
提交读(Read Commited) | N | N | Y | Y | Y |
重复读(Repeatable Read) | N | N | N | N | Y |
串行化(Serializable) | N | N | N | N | N |
四、Spring 事务管理和实现
这里简单的介绍一下声明式,使用注解方式的事务管理和基于AspectJ的xml配置方式。
<!-- 事务管理 对mybatis操作数据的事务控制,spring使用jdbc的事务控制类 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 管理事务的两种方式 --> <!-- 1、基于注释的事务,当注释中发现@Transactional时,使用id为“transactionManager”的事务管理器 --> <!-- 如果没有设置transaction-manager的值,则spring以缺省默认的事务管理器来处理事务,默认事务管理器为第一个加载的事务管理器 --> <tx:annotation-driven transaction-manager="transactionManager"/> <!-- 2、使用切面实现事务 通知 ,也可以不配置切面管理事务,可以使用annotation方式--> <!-- <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> 传播行为 <tx:method name="save*" propagation="REQUIRED"/> <tx:method name="delete*" propagation="REQUIRED"/> <tx:method name="insert*" propagation="REQUIRED"/> <tx:method name="update*" propagation="REQUIRED"/> <tx:method name="find*" propagation="SUPPORTS"/> <tx:method name="get*" propagation="SUPPORTS"/> <tx:method name="select**" propagation="SUPPORTS"/> </tx:attributes> </tx:advice>
使用单元测试进行测试事务
@Transactional @Test public void getShopUserById(){ ShopUser shopUser = new ShopUser(); shopUser.setUserid(6); shopUser.setUsername("zhangsan"); shopUser.setPassword("333"); shopUser.setCreateTime(Long.parseLong(DateUtil.getString(new Date(), DateUtil.YMDHMS))); try { shopUserService.insertSelective(shopUser); int i = 2/0; } catch (Exception e) { e.printStackTrace(); logger.info("ShopControllerTest" + e); }
转载: http://www.cnblogs.com/kristain/articles/2038397.html