对于数据库的隔离级别之前一直没有做详细整理,最近项目运行中发现了一个问题,所以抽时间对这块认真研究了下
业务场景:
服务A在处理流程中,会调用外部服务B,然后写入一条数据,服务B执行完成后,会回调服务C的接口更新服务A写入的数据。
问题:
在服务B回调服务C的时候总是找不到服务A写入的数据,在服务C中添加延时重试,问题依然存在,但此时查看数据库,对应的数据是已经存在。
先说原因吧,是因为MySQL的事务默认隔离级别是:可重复读。
在服务A调用服务B后,还没有写入数据到数据库,服务B就已经回调服务C了,服务C此时肯定是找不到对应的数据的,由于MySQL默认隔离级别是可重复读(即在一个事务中,对于同一份数据读取都是一样的),所以即使服务A已经写入了数据,服务C依然读取不到。
解决方案:
在服务C中的查询,不要放到一个事务里面,单独提取一个方法,后面的更新逻辑放到同一个事务中
什么是事务?
数据库事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。
比如:某人在商店购买100商品,其中包括两个操作:
1.该人账户减少100元
2.商店账户增加100元
这两个操作要么同时执行成功,要么同时执行失败。
数据库事务的ACID性质
- Atomic:原子性,将所有SQL作为原子工作单元执行,要么全部执行,要么全部不执行;
- Consistent:一致性,事务完成后,所有数据的状态都是一致的,即该人的账户减少了100,商店的账户必须增加100
- Isolation:隔离性,比如两个人从同一个账户取款,这两个事务对数据的修改必须相互隔离,具体隔离策略后面具体讲解。
- Duration:持久性,事务完成后,对数据库数据的修改必须被持久化存储。
数据库的隔离级别
在单个事务中,不需要做隔离,所谓数据库隔离级别是针对在并发事务的情况下,解决导致的一系列问题,这些问题包括:脏读、不可重复读、幻读,具体隔离级别如下图:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
SERIALIZABLE(串行化) | 避免 | 避免 | 避免 |
REPEATABLE READ(可重复读) | 避免 | 避免 | 允许 |
READ COMMITED(读已提交) | 避免 | 允许 | 允许 |
READ UNCOMMITED(读未提交) | 允许 | 允许 | 允许 |
SERIALIZABLE(串行化)
当两个事务同时操作数据库中相同数据时,如果第一个事务已经在访问该数据,第二个事务只能停下来等待,必须等到第一个事务结束后才能恢复运行。因此这两个事务实际上是串行化方式运行。
REPEATABLE READ(可重复读)
一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,但是不能看到其他事务对已有记录的更新。
READ COMMITTED(读已提交数据)
一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,而且还能看到其他事务已经提交的对已有记录的更新。
READ UNCOMMITTED(读未提交数据)
一个事务在执行过程中可以看到其他事务没有提交的新插入的记录,而且还能看到其他事务没有提交的对已有记录的更新。
没有事务隔离级别导致的问题
如果没有数据库的隔离级别,数据库的数据是实时变化的,即每个事务都可以读到其它事务修改后的数据,下面结合实例介绍每个场景的问题。
脏读
定义:读到未提交更新的数据
时间点 | 事务1 | 事务2 |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000 | |
T4 | 取出100后金额为900 | |
T5 | 查询账户金额为900(脏读) | |
T6 | 撤销事务,余额恢复为1000 | |
T7 | 存入100元后,金额变为1000 | |
T8 | 提交事务 |
如上事务1取出金额100后又回滚了,即啥都没做,但事务2存入了100,但最终的金额确还是1000,正确应该是1100。
在T5时间节点出现了脏读,如果数据库配置了隔离级别为SERIALIZABLE、REPEATABLE READ、READ COMMITTED,在事务1没有提交的时候,事务2读取的都是原来的值就不会出现问题。
不可重复读
定义:在同一个数据中,两次读取到的数据不一致,读到了其他数据提交更新的数据
时间点 | 事务1 | 事务2 |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000 | |
T4 | 查询账户余额为1000 | |
T5 | 取出100后金额为900 | |
T6 | 提交事务 | |
T7 | 查询账户余额为900(与T4读取的不一致) |
事务2的两次读取到的数据不一致,第二次读取到了事务1提交的数据
幻读
定义:读取到另一个事务已提交插入或删除的数据。
时间点 | 事务1 | 事务2 |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 统计一年级1班所有的学生人数为40人 | |
T4 | 一年级1班新增一名学生 | |
T5 | 提交事务 | |
T6 | 再次统计一年级1班的所有学生人数为41人 |
事务2第一次统计人数为40人,第二次统计为41人,两次统计的结果不一致,同样如果T4时间节点转走一名学生,也会出现不一致
不可重复读和幻读看起来比较类似,都是一个事务里面读取到两次不同的结果
本质的区别是:不可重复读是由于数据更新导致数据不一致导致,幻读是由于插入或删除了数据导致的。