在日常开发中处理好数据库事务问题是相当重要的,下面以MySQL、Spring为例整理了一些数据库事务的知识点。
1. 事务属性
事务属性(特性、原则)是指事务ACID属性,它们分别是:
- 原子性(Atomicity):指事务必须是一系列操作的最小单元,要么整体执行,要么整体回滚,不存在只执行了其中某一个或者某几个步骤。
- 一致性(Consistency):事务药保证数据库整体数据的完整性和业务数据的一致性,事务成功提交整体数据修改,事务错误则回滚到数据回到原来的状态。(转账后两个人余额总和还是一样)
- 隔离性(Isolation):指两个事务的执行是独立隔离的,事务之间不会相互影响,多个事务操作同一个对象时以串行等待的方式保证事务之间是隔离的。(要考虑隔离级别)
- 持久性(Durability):指事务一旦提交成功,只要修改的数据都会进行持久化,不会因为异常、宕机而造成数据错误或丢失。
2. 并发问题
事务存在的并发问题:
- 脏读:两个事务T1,T2,T1已经读取了已经被T2更新但没有提交的字段,若T2回滚,T1读取的内容就无效。
- 不可重复读:两个事务T1,T2,T1读取了一个字段,T2更新了这个字段,T1再次读取同一个字段,值就不同了。
- 幻读:两个事务T1,T2,T1从一个表中读取了一个字段,然后T2在该表插入了一些新的行,如果T1再次读取同一个表,就会多出几行。
3. SQL引擎
MySQL的SQL引擎中,只有InnoDB才支持事务,可以通过下面的指令查看MySQL的SQL引擎:
SHOW ENGINES;
其中Transactions表示SQL引擎是否支持事务:
InnoDB还支持savepoint,配合rollback to。
4. 隐式事务
可以通过下面的指令查看MySQL的隐式事务:
SHOW VARIABLES LIKE 'autocommit'
默认开启(ON)
MySQL执行INSERT,UPDATE,DELETE默认使用隐式事务。
通过下面指令可以关闭隐式事务:
set autocommit = 0
注意:上面的指令仅在当前会话有效,之后执行INSERT,UPDATE,DELETE需要手动commit;
set autocommit = 0; start transaction; -- 可选 -- insert -- update -- delete commit; -- rollback;
5. 隔离级别
MySQL支持4种隔离级别:
- read uncommitted:三种问题(脏读、不可重复读、幻读)都会出现。
- read committed:会出现不可重复读、幻读。
- repeatable read:会出现幻读。
- serializable:不会出现脏读、不可重复读、幻读。
通过下面指令查看当前数据库的事务隔离级别:
select @@tx_isolation;
MySQL默认的事务隔离级别为:repeatable read。
6. Spring事务传播
Spring事务传播是指在不同的bean之间的事务传播隔离机制,Spring事务传播方式有以下几种:
- PROPAGATION_REQUIRED:存在事务则在已存在的事务内执行,不存在事务则创建。(默认)
- PROPAGATION_SUPPORTS:存在事务则在已存在的事务内执行,不存在事务则以非事务方式执行。(可能会导致运行时的同步冲突)
- PROPAGATION_MANDATORY:存在事务则在已存在的事务内执行,不存在事务则抛出异常。
- PROPAGATION_REQUIRES_NEW:存在事务则将当前事务挂起,创建新事务执行。
- PROPAGATION_NOT_SUPPORTED:存在事务则将当前事务挂起,以非事务的方方式执行。
- PROPAGATION_NEVER:存在事务则抛出异常,以非事务的方方式执行。
- PROPAGATION_NESTED:存在事务则在已存在的事务的嵌套事务内执行,不存在则创建事务。
7. Spring事务示例
示例主要涉及的类:
- org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor.intercept(...) 非接口Service使用cglib
- org.springframework.transaction.interceptor.TransactionInterceptor.invoke(...) 事务处理拦截器
- org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(...)
测试Service:
/** * @author 米虫2022 */ @Service public class UserService { @Autowired private JdbcTemplate jdbcTemplate; @Autowired private UserTxService userTxService; /** * 查询用户 * * @param name * @return */ public User queryUser(String name) { return jdbcTemplate.query("select * from tb_user where name = ?", rs -> { if (!rs.next()) { return null; } User user = new User(); user.setName(rs.getString("name")); return user; }, name); } /** * 不开启事务插入用户数据,不管是否抛出异常,数据不回滚 * * @param user */ public void insertUser(User user) { userTxService.insert(user); } /** * 开启事务插入用户数据,抛出异常则数据回滚 * * @param user */ @Transactional(propagation = Propagation.REQUIRED) public void insertUser4Tx(User user) { userTxService.insert(user); } /** * 不开启事务并以不同的Propagation调用方法 * * @param user4Insert * @param user4PropagationInsert * @param propagation */ public void insertUser(User user4Insert, User user4PropagationInsert, Propagation propagation) { try { userTxService.insert(user4Insert); insert(user4PropagationInsert, propagation); } catch (RuntimeException e) { insert(user4PropagationInsert, propagation); throw e; } } /** * 开启事务并以不同的Propagation调用方法 * * @param user4Insert * @param user4PropagationInsert * @param propagation */ @Transactional(propagation = Propagation.REQUIRED) public void insertUser4Tx(User user4Insert, User user4PropagationInsert, Propagation propagation) { try { userTxService.insert(user4Insert); insert(user4PropagationInsert, propagation); } catch (RuntimeException e) { insert(user4PropagationInsert, propagation); throw e; } } private void insert(User user, Propagation propagation) { switch (propagation) { case REQUIRED: userTxService.insertUser4REQUIRED(user); break; case SUPPORTS: userTxService.insertUser4SUPPORTS(user); break; case MANDATORY: userTxService.insertUser4MANDATORY(user); break; case REQUIRES_NEW: userTxService.insertUser4REQUIRES_NEW(user); break; case NOT_SUPPORTED: userTxService.insertUser4NOT_SUPPORTED(user); break; case NEVER: userTxService.insertUser4NEVER(user); break; case NESTED: userTxService.insertUser4NESTED(user); break; } } }
测试用例:
/** * @author 米虫2022 */ @SpringBootTest(classes = TxStarter.class) public class UserServiceTests { @Autowired private UserService userService; @Autowired private UserTxService userTxService; // t00: 测试不开启事务,不抛异常,数据正常入库 @Test @Order(0) public void test_00() { String name = UUID.randomUUID().toString(); User user = new User(); user.setName(name); userService.insertUser(user); User ret = userService.queryUser(name); // 正常入库 Assertions.assertNotNull(ret); } // t01: 测试不开启事务,抛异常,数据也正常入库 @Test @Order(1) public void test_01() { String name = UUID.randomUUID().toString(); User user = new User(); user.setName(name); // 设置sex会抛异常 user.setSex(1); boolean error = false; try { userService.insertUser(user); } catch (Exception e) { error = true; } User ret = userService.queryUser(name); // 出现异常,但不回滚 Assertions.assertNotNull(ret); Assertions.assertTrue(error); } // t02: 测试开启事务,不抛异常,数据正常入库 @Test @Order(2) public void test_02() { String name = UUID.randomUUID().toString(); User user = new User(); user.setName(name); userService.insertUser4Tx(user); User ret = userService.queryUser(name); // 正常入库 Assertions.assertNotNull(ret); } // t03: 测试开启事务,抛异常,数据回滚 @Test @Order(3) public void test_03() { String name = UUID.randomUUID().toString(); User user = new User(); user.setName(name); // 设置sex会抛异常 user.setSex(1); boolean error = false; try { userService.insertUser4Tx(user); } catch (Exception e) { error = true; } User ret = userService.queryUser(name); // 出现异常,数据回滚 Assertions.assertNull(ret); Assertions.assertTrue(error); } // t04: 测试REQUIRED没有事务的情况下开启事务 @Test @Order(4) public void test_04() { String name4Insert = UUID.randomUUID().toString(); String name4PropagationInsert = UUID.randomUUID().toString(); User user4Insert = new User(); user4Insert.setName(name4Insert); User user4PropagationInsert = new User(); user4PropagationInsert.setName(name4PropagationInsert); // 设置sex会抛异常 user4PropagationInsert.setSex(1); boolean error = false; try { // user4Insert没有事务,user4PropagationInsert开启事务 userService.insertUser(user4Insert, user4PropagationInsert, Propagation.REQUIRED); } catch (Exception e) { error = true; } User ret = userService.queryUser(name4Insert); // 出现异常,user4Insert正常入库 Assertions.assertNotNull(ret); Assertions.assertTrue(error); ret = userService.queryUser(name4PropagationInsert); // user4PropagationInsert回滚 Assertions.assertNull(ret); } // t05: 测试SUPPORTS存在事务时,在事务内执行 @Test @Order(5) public void test_05() { String name4Insert = UUID.randomUUID().toString(); String name4PropagationInsert = UUID.randomUUID().toString(); User user4Insert = new User(); user4Insert.setName(name4Insert); User user4PropagationInsert = new User(); user4PropagationInsert.setName(name4PropagationInsert); // 设置sex会抛异常 user4PropagationInsert.setSex(1); boolean error = false; try { // user4Insert开启事务,user4PropagationInsert开启事务 userService.insertUser4Tx(user4Insert, user4PropagationInsert, Propagation.SUPPORTS); } catch (Exception e) { error = true; } User ret = userService.queryUser(name4Insert); // 出现异常,user4Insert回滚 Assertions.assertNull(ret); Assertions.assertTrue(error); ret = userService.queryUser(name4PropagationInsert); // user4PropagationInsert回滚 Assertions.assertNull(ret); } // t06: 测试SUPPORTS没有事务时,以非事务执行 @Test @Order(6) public void test_06() { String name4Insert = UUID.randomUUID().toString(); String name4PropagationInsert = UUID.randomUUID().toString(); User user4Insert = new User(); user4Insert.setName(name4Insert); User user4PropagationInsert = new User(); user4PropagationInsert.setName(name4PropagationInsert); // 设置sex会抛异常 user4PropagationInsert.setSex(1); boolean error = false; try { // user4Insert没有事务,user4PropagationInsert没有事务 userService.insertUser(user4Insert, user4PropagationInsert, Propagation.SUPPORTS); } catch (Exception e) { error = true; } User ret = userService.queryUser(name4Insert); // 出现异常,user4Insert正常入库 Assertions.assertNotNull(ret); Assertions.assertTrue(error); ret = userService.queryUser(name4PropagationInsert); // user4PropagationInsert正常入库 Assertions.assertNotNull(ret); } // t07: 测试MANDATORY开启事务时,在事务内执行 @Test @Order(7) public void test_07() { String name4Insert = UUID.randomUUID().toString(); String name4PropagationInsert = UUID.randomUUID().toString(); User user4Insert = new User(); user4Insert.setName(name4Insert); User user4PropagationInsert = new User(); user4PropagationInsert.setName(name4PropagationInsert); // 设置sex会抛异常 user4PropagationInsert.setSex(1); boolean error = false; try { // user4Insert开启事务,user4PropagationInsert开启事务 userService.insertUser4Tx(user4Insert, user4PropagationInsert, Propagation.MANDATORY); } catch (Exception e) { error = true; } User ret = userService.queryUser(name4Insert); // 出现异常,user4Insert正常回滚 Assertions.assertNull(ret); Assertions.assertTrue(error); ret = userService.queryUser(name4PropagationInsert); // user4PropagationInsert正常回滚 Assertions.assertNull(ret); } // t08: 测试MANDATORY没有事务时,抛出异常 @Test @Order(8) public void test_08() { String name4Insert = UUID.randomUUID().toString(); String name4PropagationInsert = UUID.randomUUID().toString(); User user4Insert = new User(); user4Insert.setName(name4Insert); User user4PropagationInsert = new User(); user4PropagationInsert.setName(name4PropagationInsert); // 不设置sex抛异常 boolean error = false; try { // user4Insert没有事务,user4PropagationInsert抛异常 userService.insertUser(user4Insert, user4PropagationInsert, Propagation.MANDATORY); } catch (Exception e) { error = true; } User ret = userService.queryUser(name4Insert); // SEX没有异常,user4PropagationInsert事务异常,user4Insert正常入库 Assertions.assertNotNull(ret); Assertions.assertTrue(error); ret = userService.queryUser(name4PropagationInsert); // user4PropagationInsert抛异常 Assertions.assertNull(ret); } // t09: 测试REQUIRES_NEW将事务挂起,创建新的事务执行 @Test @Order(9) public void test_09() { String name4Insert = UUID.randomUUID().toString(); String name4PropagationInsert = UUID.randomUUID().toString(); User user4Insert = new User(); user4Insert.setName(name4Insert); user4Insert.setSex(1); User user4PropagationInsert = new User(); user4PropagationInsert.setName(name4PropagationInsert); boolean error = false; try { // user4Insert有事务抛异常,user4PropagationInsert新事物 userService.insertUser4Tx(user4Insert, user4PropagationInsert, Propagation.REQUIRES_NEW); } catch (Exception e) { error = true; } User ret = userService.queryUser(name4Insert); // sex异常,user4Insert回滚 Assertions.assertNull(ret); Assertions.assertTrue(error); ret = userService.queryUser(name4PropagationInsert); // user4PropagationInsert正常入库 Assertions.assertNotNull(ret); } // t10: 测试NOT_SUPPORTED将事务挂起,以非事务的形式执行 @Test @Order(10) public void test_10() { String name4Insert = UUID.randomUUID().toString(); String name4PropagationInsert = UUID.randomUUID().toString(); User user4Insert = new User(); user4Insert.setName(name4Insert); User user4PropagationInsert = new User(); user4PropagationInsert.setName(name4PropagationInsert); user4PropagationInsert.setSex(1); boolean error = false; try { // user4Insert有事务抛异常,user4PropagationInsert无事务 userService.insertUser4Tx(user4Insert, user4PropagationInsert, Propagation.NOT_SUPPORTED); } catch (Exception e) { error = true; } User ret = userService.queryUser(name4Insert); // sex异常,user4Insert回滚 Assertions.assertNull(ret); Assertions.assertTrue(error); ret = userService.queryUser(name4PropagationInsert); // user4PropagationInsert正常入库 Assertions.assertNotNull(ret); } // t11: 测试NEVER存在事务抛异常 @Test @Order(11) public void test_11() { String name4Insert = UUID.randomUUID().toString(); String name4PropagationInsert = UUID.randomUUID().toString(); User user4Insert = new User(); user4Insert.setName(name4Insert); User user4PropagationInsert = new User(); user4PropagationInsert.setName(name4PropagationInsert); boolean error = false; try { // user4Insert有事务,user4PropagationInsert抛异常 userService.insertUser4Tx(user4Insert, user4PropagationInsert, Propagation.NEVER); } catch (Exception e) { error = true; } User ret = userService.queryUser(name4Insert); // sex异常,user4Insert回滚 Assertions.assertNull(ret); Assertions.assertTrue(error); ret = userService.queryUser(name4PropagationInsert); // user4PropagationInsert回滚 Assertions.assertNull(ret); } // t12: 测试NESTED嵌套 @Test @Order(12) public void test_12() { String name4Insert = UUID.randomUUID().toString(); String name4PropagationInsert = UUID.randomUUID().toString(); User user4Insert = new User(); user4Insert.setName(name4Insert); user4Insert.setSex(1); User user4PropagationInsert = new User(); user4PropagationInsert.setName(name4PropagationInsert); boolean error = false; try { // user4Insert异常,user4PropagationInsert抛异常 userService.insertUser4Tx(user4Insert, user4PropagationInsert, Propagation.NESTED); } catch (Exception e) { error = true; } User ret = userService.queryUser(name4Insert); // sex异常,user4Insert回滚 Assertions.assertNull(ret); Assertions.assertTrue(error); ret = userService.queryUser(name4PropagationInsert); // user4PropagationInsert回滚(主事务回滚,子事务也回滚) Assertions.assertNull(ret); } // t13: 测试同一个bean事务传播 @Test @Order(13) public void test_13() { String name = UUID.randomUUID().toString(); User user = new User(); user.setName(name); user.setSex(1); boolean error = false; try { userTxService.insert4TxProxy(user); } catch (Exception e) { error = true; } User ret = userService.queryUser(name); // 不会回滚,同一个bean不会出现事务传播隔离 Assertions.assertNotNull(ret); Assertions.assertTrue(error); } }
注意:HibernateTransactionManager 默认事关闭NESTED嵌套事务的,需要调用.setNestedTransactionAllowed(true) 启用。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)