前言:
为什么面试的时候会被问题事务相关的问题,比如事务隔离性,spring事务等。因为工作中确实会遇到啊,不了解你就处理不了遇到问题
上周同事让我帮他看一个问题. 他说同一个SQL 两次执行后,查询结果不一致, 两次查询一次有记录一次没有记录。我看他演示了一篇,演示内容如下
分析问题前需要先了解数据库事务的一些知识
事务在数据库系统中主要用于保存数据的一致性,防止数据出现错误。事务是一组包含一条或者多条语句的处理逻辑单元。 事务内的语句都会认为属于同一个处理单元, 处理过程无问题则同时成功,如果处理过程中存在异常则同时失败。
特性 | ||
1 | 原子性 (Atomicity) |
|
2 | 一致性 (Consistency) |
|
3 | 隔离性 (lsolation) |
|
4 | 持久性 (Duration) |
|
这个问题和事务的隔离性有关系, 事务的隔离性就需要提到事务的隔离级别
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交(read-uncommitted) | 是 | 是 | 是 |
不可重复读(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
Oracle支持两种事务隔离级别:READ COMMITTED(读已提交为默认事务隔离级别)和 SERIALIZABLE,MySql 默认的事务隔离级别为REPEATABLE READ,从这里可以看出比较MySQL, Oracle 会出现“不可重复读” 的问题
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 (串行化) | 这是花费最高代价但是最可靠的事务隔离级别,事务被处理为顺序执行。除了防止脏读、不可重复读外,还避免了幻像读 |
了解上面的知识点后,回头看同事提到的的问题。
一次请求后调用Spring框架中的某个服务接口,Spring框架使用事务管理器管理请求产生的事务, Spring事务管理器默认使用REQUIRED事务传播,这样此次请求的所有数据库访问都是同一个事务中处理。 Spring事务管理默认使用数据库事务,当前数据库使用的是Oracle, Oracle的事务隔离级别为“读已提交”,说明只有事务COMMIT后才能被其他事务读到,事务COMMIT之前,其他事务无法读到本次修改的数据。
第二次查询结果为空集合, 可能是由于修改的数据引起SQL中的某些条件不满足,造成查询结果为空, 而其他的事物由于看不到修改所以能查询到数据,
分析代码。 结果和想的相同。 梳理下问题产生的过程如下图所示: