并发操作可能出现的问题

问题

描述

第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数据库并发量 mysql数据库并发问题_mysql


设置事务隔离级别

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;