一、什么是事务
事务是一个对数据库操作的序列,是一个不可分割的工作单位,要不这个序列里面的操作全部执行,要不全部不执行。
事务具有四个特性(ACID):
原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。;
一致性(Consistency):事务前后的数据完整性要保证一致 。比如A向B转账,不可能A扣了钱,B却没收到;
隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账;
持久性(Durability):事务完成后,数据不随着外界原因导致数据丢失,事务对数据库的所有更新将被保存到数据库,不能回滚;
二、事务的并发问题
1、事务隔离所导致的问题
1.1、脏读
指一个事务读取了另一个事务未提交的数据。如事务 A 读取了事务 B 更新的数据,然后 B 回滚操作,那么 A 读取到的数据是脏数据。
1.2、不可重复读
指在一个事务里面读取了两次某个数据,读出来的数据不一致。(这个不一定是错误,只是某些场合不对)
1.3、幻读
是指在一个事务内读取到了别的事务插入的数据,导致前后读取数量总量不一致。(一般是行影响,多了一行)
2、事务隔离级别
事务隔离级别,就是为了解决上面几种问题而诞生的。为什么要有事务隔离级别,因为事务隔离级别越高,在并发下会产生的问题就越少,但同时付出的性能消耗也将越大,因此很多时候必须在并发性和性能之间做一个权衡。
事务隔离级别有 4 种,但是像 Spring 会提供给用户 5 种:
2.1、 默认级别(default)
默认隔离级别,每种数据库支持的事务隔离级别不一样,如果Spring配置事务时将isolation设置为这个值的话,那么将使用底层数据库的默认事务隔离级别。
2.2、读未提交(read_uncommitted)
读未提交,即能够读取到没有被提交的数据,最低隔离级别,无法解决脏读、不可重复读、幻读中的任何一种问题。
2.3、读已提交(read_committed)
读已提交,即能够读到那些已经提交的数据,可避免脏读情况发生。
2.4、可重复读(repeatable_read)
可重复读,即在数据读出来之后加锁,类似”select * from XXX for update”,明确数据读取出来就是为了更新用的,所以要加一把锁,防止别人修改它。REPEATABLE_READ的意思也类似,读取了一条数据,这个事务不结束,别的事务就不可以改这条记录,可避免脏读、不可重复读情况的发生。
2.5、串行化(serializable)
串行化,最严格的级别,事务串行执行,资源消耗最大;,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务。可避免脏读、不可重复读、虚读情况的发生。
隔离级别从低到高为:读未提交、读已提交、可重复读、串行化。
不是事务隔离级别设置得越高越好,事务隔离级别设置得越高,意味着势必要花手段去加锁用以保证事务的正确性,数据库并发性能就要降低。
在实际业务场景中,可以根据需求选择合适的级别,如设置为 READ_COMMITED,此时避免了脏读,并发性也还不错,之后可再通过一些别的手段去解决不可重复读和幻读的问题。
3、事务隔离级别查看及修改
3.1、查看事务隔离级别
1 | SELECT @@transaction_isolation |
3.2、修改当前会话事务隔离级别
1 | -- (参数可以为:READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE) |
比如 MyBatis,getSqlSession() 的时候,只针对这一次拿到的 Session 有效。
3.3、修改全局事务隔离级别
1 | SET global TRANSACTION ISOLATION LEVEL REPEATABLE READ; |
针对此后所有的会话有效,当前已经存在的会话不受影响。
三、使用事务保障数据的完整性
1、事务自动提交
MySQL 中不是每个引擎都支持事务的,查看所使用的存储引擎是否支持事务可以通过命令查看:
1 | SHOW engines; |
若无特殊需求,存储引擎通常采用 innodb。
注意, MySQL 默认是开启事务自动提交的。查看是否开启了事务自动提交:
1 | SELECT @@autocommit; |
设置自动提交:
1 | SET autocommit = 0; -- 关闭自动提交 |
2、SQL 模拟事务
1 | -- 模拟事务执行流程 |
3、SpringBoot 中使用事务
SpringBoot 使用事务非常简单,首先使用注解 @EnableTransactionManagement 开启事务支持后,然后在访问数据库的Service方法上添加注解 @Transactional 即可。
当开启事务管理的 Service 方法出现异常时,已提交的数据操作将被回滚。
在启动类中开启事务管理支持:
1 | import org.springframework.boot.SpringApplication; |
使用事务管理保障更新操作的数据完整性:
1 |
|
3.1、Try-Catch 致事务失效的问题
事务就是从方法开始到结束过程中,只要出现异常,为了保障数据完整性,会将出现异常之前所做的数据处理回滚。
但方法中难免会使用 try catch 处理异常,若 catch 到了异常,事务将不会触发回滚,因为事务只有在 runtimeException 的时候才会触发回滚。目前解决方法有两个,分别是:手动抛出运行时异常和手动回滚。
(1)在 catch 中手动抛出运行时异常
1 | try { |
(2)在 catch 中手动回滚
1 | try { |
四、总结
事务管理是保障数据增删改查操作数据完整性的有效手段。但当线程并发时,事务的隔离性会导致数据读取出现异常,为了保证数据读取的准确性,数据库设计了事务隔离级别。
事务隔离级别,是通过线程同步锁实现的。并发问题会随着事务隔离级别的变高而减少。但数据库异步并发性能却会相应降低。
数据需要事务保障完整性,但事务隔离性产生的并发问题需要谨慎处理。
2021年11月16日 稿