作为CURD 码农,事务管理是我开发中经常写的或者考虑的内容,本文将结合spring 框架讲一下我在开发中遇到的问题。
事务
事务的四个特点:Atomicity,Consistency,Isolation,Durability。
Atomictiy :一个事务当成一个单位,所有的数据库操作要么全部执行成功,要么全部执行失败。
Consistency: 保证事务将数据库从一种有效状态带到另一种有效状态。根据所有定义的规则和约束,写入数据库的所有数据必须是有效的。
Isolation:当多个事务并发处理数据时,根据不同的隔离级别,事务之间数据的可见性不一样。四个隔离级别:Read Uncommited,Read Commited, Repeatable Read, Serializable。
事务并发问题:
- 脏读,在事务A 读到了事务B 未提交的数据,如果事务B 因为异常回滚了,读取到的就是脏数据。当前事务隔离级别未读未提交
- 不可重复读,在事务A读到的数据第一次与第二次读到的数据不一致;因为读到的数据在事务B 修改提交之后,在事务A 就能跟读到。当前事务隔离级别为读已提交
- 幻读,在事务中,第一次读到的数据,与第二次读到的数据条目不一致,第一次可能是一条,第二次读到了两条;因为另外的事务读到的; 当前事务隔离级别为可重复读
MySQL (InnoDB): 默认为REPEATABLE READ
Durability: 一个事务一旦提交就会被保存,即使是断电,系统crash了
Spring 事务托管
使用@Transacational 注解托管事务,有AOP 动态代理编织方法;实现事务自动创建自动提交自动回滚。
在使用注解之前要确保在Spring 配置中启用了事务管理。
@Transactional 注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Transactional { @AliasFor("transactionManager") String value() default ""; @AliasFor("value") String transactionManager() default ""; String[] label() default {}; Propagation propagation() default Propagation.REQUIRED; Isolation isolation() default Isolation.DEFAULT; int timeout() default TransactionDefinition.TIMEOUT_DEFAULT; String timeoutString() default ""; boolean readOnly() default false; Class<? extends Throwable>[] rollbackFor() default {}; String[] rollbackForClassName() default {}; Class<? extends Throwable>[] noRollbackFor() default {}; String[] noRollbackForClassName() default {}; }
|
@Transactional 注解可以在方法上使用,也可以在类上使用。
在类上使用时,所有的public 方法都会被事务管理,但是颗粒度较大,使用不当会造成没有必要的事务开销。
transactionManager:事务管理器
timeout: 事务超时时间,默认-1,单位秒
rollbackFor : 指定异常回滚
nonRollbackFor: 指定异常不回滚
label: 标签,描述用
readOnly : 是否只读,标识之后事务使用只读模式
isolation 隔离级别: 虽然在代码里面可以设置隔离级别,但是实际的隔离级别以数据库设置的为准,所以一般也不会在编码的时候设置这个参数.
propagation 传播级别
- REQUIRED: 默认传播级别,支持当前事务,如果当前存在事务就继续使用,如果不存在则创建一个新事务
- SUPPORTS:支持当前事务,如果当前不存在事务,就保持不在事务里面
- MANDATORY:支持当前事务,如果事务不存在,则抛出异常
- REQUIRES_NEW:创建一个新的事务,如果当前存在一个事务,则suspend 当前事务
- NOT_SUPPORTED:保证当前无事务存在,如果存在事务则将当前事务suspend
- NEVER:无事务执行,如果存在事务则抛出异常
- NESTED :如果当前事务存在,则嵌套在当前事务中执行。
@Transactional 注解失效
为什么我加了@Transactional 注解但是事务没生效,导致数据不一致?
前面我们提到,Spring 事务管理是通过AOP 动态代理实现的,如果没有生效,应该要看看使用了注解的方法或者类,是否被spring 管理.
手工管理事务
另外一种常见的方法就是手工管理事务,主要是下面三个组件
PlatformTransactionManager
TranscationDefinition, 事务相关属性定义,属性与注解的一致
DefaultTransactionDefinition,
TransactionStatus,事务状态对象
TransactionExecution : 事务当前执行状态
SavepointManager: 编程式事务管理的接口
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @Service public class TransactionDemoService { @Autowired PlatformTransactionManager platformTransactionManager; public void func(int a) { DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition(); defaultTransactionDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); defaultTransactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); defaultTransactionDefinition.setTimeout(100); TransactionStatus transaction = platformTransactionManager.getTransaction(defaultTransactionDefinition); try { if(a == 100) { return ; } Object savepoint = transaction.createSavepoint(); transaction.rollbackToSavepoint(savepoint); Object secondSavepoint = transaction.createSavepoint(); transaction.releaseSavepoint(secondSavepoint); platformTransactionManager.commit(transaction); } catch (Exception e) { platformTransactionManager.rollback(transaction); } finally { if (!transaction.isCompleted()) { platformTransactionManager.rollback(transaction); } } } }
|
ps: 使用try-catch-finally 方法管理事务 ,避免因为其他业务逻辑直接return 而没有提交事务
使用TransactionTemplate
Spring 也提供了TransactionTemplate 进行手工事务管理
源码:TransactionTemplate#execute 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @Override @Nullable public <T> T execute(TransactionCallback<T> action) throws TransactionException { Assert.state(this.transactionManager != null, "No PlatformTransactionManager set"); if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) { return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action); } else { TransactionStatus status = this.transactionManager.getTransaction(this); T result; try { result = action.doInTransaction(status); } catch (RuntimeException | Error ex) { rollbackOnException(status, ex); throw ex; } catch (Throwable ex) { rollbackOnException(status, ex); throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception"); } this.transactionManager.commit(status); return result; } }
|
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Autowired private TransactionTemplate transactionTemplate;
public void func() { transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); Object execute = transactionTemplate.execute(callback -> { Object savepoint = callback.createSavepoint(); try { return null; } catch (Exception e) { callback.rollbackToSavepoint(savepoint); } return null; }); }
|
一些实践
- 避免长事务
- 先做业务逻辑判断,再进行数据库写操作
- 避免大事务
- 减少慢SQL,查询要走索引
- 扫表要进行深分页,加主键索引作为查询条件
- 考虑主从延迟,避免空指针,从库查不到查主库
参考