ACID:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
原子性:是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
一致性:是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
隔离性:是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
持久性:是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
个人理解据库事务主要做了两件事情:
一是保证一致性结果,能在发生异常的时候快速恢复,也就是回滚
二是并发访问的时候提供隔离
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
未提交读Read Uncommitted | 是 | 是 | 是 |
已提交读Read Committed | 否 | 是 | 是 |
可重复读Repeatable Read | 否 | 否 | 是 |
可串行化Serializable | 否 | 否 | 否 |
读未提交:就是一个事务可以读取另一个未提交事务的数据。
读提交:就是一个事务要等另一个事务提交后才能读取数据。若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。
重复读:就是在开始读取数据(事务开启)时,不再允许修改操作。重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。
Serializable:是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
mysql 对应的InnoDB默认隔离级别是 重复读
明细可以参考:https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html
隔离怎么实现?
隔离的实现也是依赖加锁机制, InnoDB存在两种锁:
共享锁(S锁) :(插入/修改/删除)资源获取S锁之后,能加S锁,不能加X锁
排它锁(X锁) : 资源加上X锁之后,不能加S锁,也不能加X锁
S/X锁兼容表
锁定类型 | 读锁 | 写锁 |
---|---|---|
读锁 | Compatible | Conflict |
写锁 | Conflict | Conflict |
具体的四种隔离级别锁实现:
隔离级别 | 操作 | 锁 | 生命周期 |
---|---|---|---|
读未提交 | 读 | 否 | |
读未提交 | 写 | 行级排它锁 | 立即释放 |
读已提交 | 读 | 行级共享锁 | 立即释放 |
读已提交 | 写 | 行级排它锁 | 事务结束 |
可重复读 | 读 | 行级共享锁 | 事务结束 |
可重复读 | 写 | 行级排它锁 | 事务结束 |
可串行化 | 读 | 范围锁/表级别锁 | 事务结束 |
可串行化 | 写 | 行级排它锁 | 事务结束 |
读未提交
为了解决丢失更新问题,需要对写操作加 X 锁
读已提交
为了保证读已提交,读操作加上 S 锁,这样如果其他事务有正在写的操作,必须等待写操作提交之后才能读,因为 S 和 X 互斥,如果在读的过程中其他事务想写,也必须等事务读完之后才可以。这里的 S 锁是一个临时 S 锁,表示事务读完之后立即释放该锁,可以让其他事务继续写,如果事务再读的话,就可能读到不一样的记录,这就是 不可重复读 了。
可重复读
为了让事务可以重复读,加在读操作的 S 锁变成了持续 S 锁,也就是直到事务结束时才释放该锁,这可以保证整个事务过程中,其他事务无法进行写操作,所以每次读出来的记录是一样的。
可串行化
序列化隔离级别下单纯的使用行锁已经实现不了,因为行锁不能阻止其他事务的插入操作,这就会导致幻读问题,这种情况下,我们可以把锁加到表上或者使用范围锁(间隙锁)。
通过对锁的类型(读锁还是写锁),锁的粒度(行锁还是表锁),持有锁的时间(临时锁还是持续锁)合理的进行组合,就可以实现四种不同的隔离级别.这四种不同的加锁策略实际上又称为 封锁协议(Locking Protocol)
Spring事务隔离级别
- ISOLATION_DEFAULT这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应 。默认采用的是重复读,除了insert幻读外基本的场景都能cover
- ISOLATION_READ_UNCOMMITTED 这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读
- ISOLATION_READ_COMMITTED 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。
- ISOLATION_REPEATABLE_READ 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
- ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。
Spring事务传播级别
- PROPAGATION_REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
- PROPAGATION_SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。
- PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
- PROPAGATION_REQUIRES_NEW 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。当数据库分库分表存在跨库的时候需要启用
- PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。
- PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常。
-
PROPAGATION_NESTED 如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。
demo如下
<!--事务模板 -->
<bean id="transactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager">
<ref local="transactionManager" />
</property>
<!--ISOLATION_DEFAULT 表示由使用的数据库决定 -->
<property name="isolationLevelName" value="ISOLATION_DEFAULT"/>
<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
</bean>