并发操作可能出现的问题
问题 | 描述 |
第1类丢失更新 | 事务A撤销时,把已经提交的事务B的更新数据覆盖了 |
第2类丢失更新 | 事务A覆盖事务B已经提交的数据,造成事务B所做的操作丢失。 |
脏读 | A事务读取B事务尚未提交的数据并在此基础上操作,而B事务执行回滚,那么A读取到的数据就是脏数据。 |
幻读 | 事务A重新执行一个查询,返回一系列符合查询条件的行,发现其中插入了被事务B提交的行 |
不可重复读 | 事务A重新读取前面读取过的数据,发现该数据已经被另一个已提交的事务B修改过了 |
- 第1类丢失更新:事务A撤销时,把已经提交的事务B的更新数据覆盖了
时间 | 取款事务A | 转账事务B |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 汇入100元修改余额为1100元 | |
T6 | 提交事务 | |
T7 | 取出100元将余额修改为900元 | |
T8 | 撤销事务 | |
T9 | 余额恢复为1000元(丢失更新) |
- 第2类丢失更新:事务A覆盖事务B已经提交的数据,造成事务B所做的操作丢失。
时间 | 转账事务A | 取款事务B |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 取出100元将余额修改为900元 | |
T6 | 提交事务 | |
T7 | 汇入100元将余额修改为1100元 | |
T8 | 提交事务 | |
T9 | 查询账户余额为1100元(丢失更新) |
- 脏读(Dirty Read):A事务读取B事务尚未提交的数据并在此基础上操作,而B事务执行回滚,那么A读取到的数据就是脏数据。
时间 | 转账事务A | 取款事务B |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 取出500元余额修改为500元 | |
T5 | 查询账户余额为500元(脏读) | |
T6 | 撤销事务余额恢复为1000元 | |
T7 | 汇入100元把余额修改为600元 | |
T8 | 提交事务 |
- 幻读(Phantom Read):事务A重新执行一个查询,返回一系列符合查询条件的行,发现其中插入了被事务B提交的行。
时间 | 统计金额事务A | 转账事务B |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 统计总存款为10000元 | |
T4 | 新增一个存款账户存入100元 | |
T5 | 提交事务 | |
T6 | 再次统计总存款为10100元(幻读) |
- 不可重复读(Unrepeatable Read):事务A重新读取前面读取过的数据,发现该数据已经被另一个已提交的事务B修改过了。
时间 | 转账事务A | 取款事务B |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 取出100元修改余额为900元 | |
T6 | 提交事务 | |
T7 | 查询账户余额为900元(不可重复读) |
如何处理
使用锁机制:
mysql中设置事务隔离级别,自动加锁
读未提交 – 会发生脏读。
读提交 – 会发生不可重复读,不会读到脏数据。
可重复读 – 重复读到之前的数据,在一个事务中。
隔离级别 | 第一类丢失更新 | 第二类丢失更新 | 脏读 | 幻读 | 不可重复读 |
READ UNCOMMITED | 不允许 | 允许 | 允许 | 允许 | 允许 |
READ COMMITTED | 不允许 | 允许 | 不允许 | 允许 | 允许 |
REPEATABLE READ | 不允许 | 不允许 | 不允许 | 允许 | 不允许 |
SERIALIZABLE | 不允许 | 不允许 | 不允许 | 不允许 | 不允许 |
需要说明的是,事务隔离级别和数据访问的并发性是对立的,事务隔离级别越高并发性就越差。所以要根据具体的应用来确定合适的事务隔离级别,这个地方没有万能的原则。
测试不同隔离级别下并发操作出现的问题
在Linux系统中进行测试
因为mysql是自动提交的,首先开启事务环境
使用 begin 或者 start transaction 都可以开启一个事务环境
开启事务环境
mysql> begin;
mysql> start transaction ;
提交事务
mysql> commit;
回滚事务
mysql> rollback;
查看事务
mysql> select @@tx_isolation;
设置事务隔离级别
mysql> set session transaction isolation level read uncommitted;
同时开启两个窗口,对同一个数据进行操作,对并发操作出现的问题进行测试
- 场景一:读脏数据
事务A:
~ begin;
~ update tb_emp set sal=2800 where eno=1359;
事务B:
~ set session transaction isolation level read uncommitted;
~ begin;
~ select * from tb_emp; ---> 读脏数据
~ commit;
事务A:
~ rollback;
- 场景二:不可重复读
事务B:
~ set session transaction isolation level read committed;
~ begin;
~ select * from tb_emp where sal<3000;
事务A:
~ begin;
~ update tb_emp set sal=3800 where eno=1359;
~ commit;
事务B:
~ select * from tb_emp wehre sal<3000; ---> 没有1359对应的记录了,不可重复读
~ commit;