Spring 事务的使用
Spring 事务有编程式事务和声明式事务。
常用的声明式事务使用 @Transaction 注解就可以开启事务。编程式事务不常用暂且不表。
@Transaction 是有参数的,我们可以设置事务的隔离级别,如:
@Transactional(isolation = Isolation.REPEATABLE_READ)
想要知道参数的意义,我们就要知道 Spring 事务的原理。
Spring 事务的原理
Spring 事务其实就是数据库事务。
事务有四种隔离级别,使用如下:
读未提交
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
读已提交
@Transactional(isolation = Isolation.READ_COMMITTED)
可重复读
@Transactional(isolation = Isolation.REPEATABLE_READ)
串行化
@Transactional(isolation = Isolation.SERIALIZABLE)
下面我们依次讲解这四个隔离级别有什么用。
![58144b501e0146acf286abdeb60e3499.png](https://i-blog.csdnimg.cn/blog_migrate/fcb140c19e5c221a62586313a551ed8f.jpeg)
我们先以 MySQL 数据库为例子,MySQL 的 InnoDB 引擎支持事务,提供了 行锁 和 表锁 。
先下定义,加锁 是为了防止并发的时候出错。 行锁 的性能要优于 表锁,不加锁 的性能要优于 行锁。
得到结论,保证在并发不出错的情况下能 不加锁 就 不加锁,能加 行锁 就不要加 表锁。
ok,下面开始分析四个隔离级别都加的什么锁。
读未提交。
这个级别不加锁,那么程序里使用这个隔离级别会出现什么问题呢?
如果都是 select 语句,什么问题都不会有,就用这个级别。如果有 update 语句,那就可能有问题了。
我们先提出这么一个问题:
假如一个事务 A 先对某行数据进行修改,然后又改回去了。如果有另一个事务 B ,刚好卡在 A 的两个操作中间对这行数据进行了一个读取,那么读到的就是脏数据。
读已提交。
针对上一个隔离级别的问题怎么改进呢?对将要修改的数据加行锁(对 update 语句加锁),注意,注意,注意,加锁了,安全了但是性能下降了。事务 A 对这行数据要修改,先加个锁,直到结束才释放锁,事务 B 就没办法卡在 A 的操作中间对数据进行操作了。
但仍然有问题:
假如一个事务 A 先对某行数据进行读取,然后进行加一保存。如果有另一个事务 B ,刚好卡在 A 的两个操作中间对这行数据进行了一个读取并加一保存,那么数据就混乱了。
这个问题也可以表述为:
假如一个事务 A 先对某行数据进行读取,然后进行读取。如果有另一个事务 B ,刚好卡在 A 的两个操作中间对这行数据进行了一个修改,那么 A 两次读取的数据就不一样了。
第一个表述更好理解,第二个表述主要为了突出了 不可重复读 这几个字,迎合下一个级别 可重复读。
可重复读。
针对上一个隔离级别的问题怎么改进呢?对将要读取的数据也加行锁(对 select 语句加锁),注意,注意,注意,上一个只有 update 语句加锁,这个连 select 语句也加锁了,安全了但是性能下降了。事务 A 对这行数据要读取,先加个锁,直到结束才释放锁,事务 B 就没办法卡在 A 的读取的中间对数据进行操作了。
这个隔离级别仍然有问题:
假如事务 B 有一个 insert 语句,卡在事务 A 的某些语句中间插了一条数据,那么就可能造成数据混乱,比如,我们对整个表操作, x 字段的值加 50,y 字段的值减 50,事务 B 卡在中间插入的数据只执行了 y 字段减 50,数据就混乱了。
串行化。
针对上一个隔离级别的问题,串行化加的是表锁,什么问题都不会有,但是性能很差。
综上所述,事务隔离级别的选择其实是我们程序并发情况下会出哪种错误,就用相应的隔离级别,在并发不出错的前提下,尽可能的不加锁,或者加行锁而不是表锁,这样才能提高性能。