前言:
为什么面试的时候会被问题事务相关的问题,比如事务隔离性,spring事务等。因为工作中确实会遇到啊,不了解你就处理不了遇到问题
1.问题现象
上周同事让我帮他看一个问题. 他说同一个SQL 两次执行后,查询结果不一致, 两次查询一次有记录一次没有记录。我看他演示了一篇,演示内容如下
- 在业务处理中查询SQL设置了一个断点
- 发布一次业务请求, 业务处理后,程序第一次运行到断点时,查看结果集中存在1条记录
- 继续执行业务处理
- 程序再次执行到查询SQL的断点时,查看结果集中集合为空集合
- 将查询的SQL截取处理,在数据库中运行,查询结果有1条所需的数据
2.问题分析
分析问题前需要先了解数据库事务的一些知识
2.1认识事务
事务在数据库系统中主要用于保存数据的一致性,防止数据出现错误。事务是一组包含一条或者多条语句的处理逻辑单元。 事务内的语句都会认为属于同一个处理单元, 处理过程无问题则同时成功,如果处理过程中存在异常则同时失败。
2.2事务的CAID特性
特性 | ||
1 | 原子性 (Atomicity) |
|
2 | 一致性 (Consistency) |
|
3 | 隔离性 (lsolation) |
|
4 | 持久性 (Duration) |
|
2.3事务隔离
这个问题和事务的隔离性有关系, 事务的隔离性就需要提到事务的隔离级别
- READ UNCOMMITTED(读未提交数据): 允许事务读取未被其他事务提交的变更数据,会出现脏读、不可重复读和幻读问题。
- READ COMMITTED(读已提交数据): 只允许事务读取已经被其他事务提交的变更数据,可避免脏读,仍会出现不可重复读和幻读问题。
- REPEATABLE READ(可重复读): 确保事务可以多次从一个字段中读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读,仍会出现幻读问题。
- SERIALIZABLE(序列化): 确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,可避免所有并发问题,但性能非常低。
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交(read-uncommitted) | 是 | 是 | 是 |
不可重复读(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
Oracle支持两种事务隔离级别:READ COMMITTED(读已提交为默认事务隔离级别)和 SERIALIZABLE,MySql 默认的事务隔离级别为REPEATABLE READ,从这里可以看出比较MySQL, Oracle 会出现“不可重复读” 的问题
2.4 Spring事务
Spring事务默认使用数据库事务,当一次请求发起后,调用Spring的某个带有事务的Service时候,会开启一个事务,当执行完成后事务COMMIT提交
Spring事务的传播属性列表
属性 | 描述 | |
1 | REQUIRED(默认) | 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。 被设置成这个级别时,会为每一个被调用的方法创建一个逻辑事务域。如果前面的方法已经创建了事务,那么后面的方法支持当前的事务,如果当前没有事务会重新建立事务 |
2 | MANDATORY | 支持当前事务,如果当前没有事务,就抛出异常 |
3 | NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常 |
4 | NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起 |
5 | REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起 |
6 | SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行 |
7 | NESTED | 支持当前事务,新增Savepoint点,与当前事务同步提交或回滚 |
Spring事务隔离级别列表
属性 | 描述 | |
1 | DEFAULT (默认) | 使用数据库默认的事务隔离级别 |
2 | READ_UNCOMMITTED (读未提交) | 这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读 |
3 | READ_COMMITTED (读已提交) | 保证一个事务修改的数据提交后才能被另外一个事务读取,另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读 |
4 | REPEATABLE_READ (可重复读) | 这种事务隔离级别可以防止脏读、不可重复读,但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了不可重复读 |
5 | SERIALIZABLE (串行化) | 这是花费最高代价但是最可靠的事务隔离级别,事务被处理为顺序执行。除了防止脏读、不可重复读外,还避免了幻像读 |
3 问题原因
了解上面的知识点后,回头看同事提到的的问题。
一次请求后调用Spring框架中的某个服务接口,Spring框架使用事务管理器管理请求产生的事务, Spring事务管理器默认使用REQUIRED事务传播,这样此次请求的所有数据库访问都是同一个事务中处理。 Spring事务管理默认使用数据库事务,当前数据库使用的是Oracle, Oracle的事务隔离级别为“读已提交”,说明只有事务COMMIT后才能被其他事务读到,事务COMMIT之前,其他事务无法读到本次修改的数据。
第二次查询结果为空集合, 可能是由于修改的数据引起SQL中的某些条件不满足,造成查询结果为空, 而其他的事物由于看不到修改所以能查询到数据,
分析代码。 结果和想的相同。 梳理下问题产生的过程如下图所示:
- Service服务接口开始处理并开启了一个事务T,事务T开始时查询数据库数据此时获取结果为R,
- 业务过程中修改过表中数据D,将某个字段设置为空, 因事务隔离性只有事务T能看到修改内容,此时再使用之前的SQL查询,因为数据发生了变化,字段空让数据R在查询时被被过滤,所以第二次查询不到数据。
- 因为事务T的事务没有提交。数据库中的数据还是修改之前的数据, 所以其他事务在数据库中执行查询SQL,还能查询出第一次查询看到的数据