数据库并发问题
脏读
事务A读取到了事务B未提交的数据,当事务B回滚后,事务A读取到的就是脏数据。
不可重复读
同一事务在事务内多次读取,读取到的数据不一致。例:在事务A的第一次读取和第二次读取中间,事务B更新或删除了数据,导致事务A两次读取到不同的数据,即不可重复读。
幻读
同一事务在事务内多次读取,读取到的记录数不一样。例:在事务A的第一次读取和第二次读取中间,事务B插入了新的记录(此记录符合事务A的查询条件),导致事务A读取到的记录数不一样,即幻读。
数据库隔离级别
MySQL数据库有4种隔离级别,隔离级别依次递增,相应的系统开销也依次递增。
未提交读:Read uncommitted
mysql> set [global|session] transaction isolation level read uncommitted;
本事务读取了别的事务未提交的数据,即脏读。下面以并发的A事务和B事务对银行账户的金额操作进行说明:
A事务 | B事务 |
查询余额 mysql> select money from t_account where id=1234567; +-------+
| |
开启事务 mysql>start transaction; | |
更新余额 mysql> update t_account set money=10001 where id=1234567; | 开启事务 mysql> start transaction; |
查询余额 mysql> select money from t_account where id=1234567; +-------+ | |
回滚事务 mysql> rollback; | 其它事务操作 mysql> |
提交事务 mysql> commit; |
从上面表格可以看出,B事务在A事务更新了余额且未提交事务时,读取到了脏数据money=10001。
由于可以读到别的事务未提交的数据,所以同样会有不可重复读和幻读的情况发生,此隔离级别很少用于实际应用中。
已提交读:Read committed
mysql> set [global|session] transaction isolation level read committed;
本事务只能读取到别的事务提交后的数据。即事务未提交时,处于中间状态(不一致状态)的数据对其它事务是不可见的。下面以并发的A事务和B事务对银行账户的金额操作进行说明:
A事务 | B事务 |
查询余额 mysql> select money from t_account where id=1234567; +-------+
| |
开启事务 mysql>start transaction; | |
更新余额 mysql> update t_account set money=10001 where id=1234567; | 开启事务 mysql> start transaction; |
查询余额 mysql> select money from t_account where id=1234567; +-------+ | |
提交事务 mysql> commit; |
|
查询余额 mysql> select money from t_account where id=1234567; +-------+ | |
提交事务 mysql> commit; |
从上面表格可以看出,B事务第一次查询余额时,A事务还未提交,所以读到的数据仍是A事务开始前的值money=10000。当B事务第二次查询余额时,此时A事务已提交,B事务读到的数据亦是A事务更新后的数据,即money=10001。
已提交读避免了脏读的情况,但仍然有不可重复读和幻读的情况发生。
可重复读:Repeatable read
mysql> set [global|session] transaction isolation level repeatable read;
对同一数据的多次读取,结果是一致的,除非数据被自身事务所修改。可重复读可以阻止脏读和不可重复读,但仍然有幻读的情况发生。
可序列化:Serializable
mysql> set [global|session] transaction isolation level serializable;
最高的隔离级别,完全服从ACID的隔离级别。所有事务依次逐个执行,这样事务之间就完全不可能产生干扰。该隔离级别可阻止脏读、不可重读和幻读。
以上4种隔离级别和相应的副作用关系如下:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
未提交读 | ✅ | ✅ | ✅ |
已提交读 | ❎ | ✅ | ✅ |
可重复读 | ❎ | ❎ | ✅ |
可序列化 | ❎ | ❎ | ❎ |