多线程并发执行多个事务的业务逻辑如下:
多个事务对缓存页中的同一条数据同时进行更新或者查询,可能产生以下四种问题:
脏写、脏读、不可重复读、幻读。
1. 脏写
有两个事务,事务A和事务B,同时在更新一条数据,数据的值为Null,事务A将数据的值更新为A,事务B紧接着将数据的值更新为B。这个时候事务A发生了回滚,就会把数据回滚到更新之前的Null值。对于事务B来说,更新的数据没了,这就是脏写。
其本质是:事务B修改了事务A修改过的值,但此时事务A没有提交,所以事务A可能发生回滚,导致事务B的数据丢失,这就是脏写的定义。
2.脏读
有两个事务,事务A和事务B,同时在操作一条数据,数据的值为Null,事务A将数据的值更新为A,事务B查询这条数据,查的结果是A值,事务B使用查到的这个值进行业务处理。这个时候事务A发生了回滚,把数据回滚到更新之前的Null值。事务B再次查询数据,获得的结果是Null值,与之前的查询不一致,这就是脏读。
其本质是:事务B查询事务A修改过的数据,但此时事务A没有提交,所以事务A可能发生回滚,导致事务B再次查询时读取不到刚才的数据了,这就是脏读的定义。
无论是脏读还是脏写,都是因为一个事务去更新或查询了另一个还没有提交的事务更新过的数据。由于另一个事务还没有提交,可能会发生回滚,就会导致更新或者查询的数据没有了。
3. 不可重复读
有三个事务,事务A、B、C,事务A中会对一条数据进行多次查询,事务B、事务C都会对同一条数据进行更新。
现在假设有一个前提,事务未提交之前,其他事务无法获取到当前事务修改的内容。即事务B更新了数据,如果没有提交事务,事务A就读取不到,只有事务B提交之后,修改的内容才会被事务A读取到。这种情况可以避免脏读的发生。
假设缓存页中的一条数据原来的值是A,事务A第一次查询,读取得到A值。
接着事务B对数据进行更新,更新为B值,并立刻提交了事务。但事务A还在执行中,第二次对数据进行查询,得到事务B修改,并已经提交后的数据B值。
然后事务C对数据进行更新,更新为C值,并立刻提交了事务。但事务A还在执行中,第三次对数据进行查询,得到事务C修改,并已经提交后的数据C值。
这种情况对于不同的业务场景来说可能会有问题:
事务A第一次查询到的是A值,如果希望在后面的事务执行期间,多次查询都是同样的A值,这样就是希望数据是可重复读的。
但实际是,A值是不可重复读的,因为事务B和事务C更新值并提交事务之后,事务A读取的结果就变了。这样数据就是不可重复读的,这可能就是一种问题。
简单来说就是:
如果期望的是可重复读,但数据库的表现是不可重复读,事务A执行期间多次查询的值不一致,是其他事务修改后的值,就可以认为数据有问题,这个问题是“不可重复读”的问题。
当然,如果期望的是不可重复读,那么数据库就是正常的表现。
4.幻读
有一个事务A,先发送一条SQL语句,里面有一个条件查询:“select * from table where id > 10;”,第一次查询出10条数据。
接着事务B往表中插入两条记录,并提交了事务。这样表中就多了几条数据。
接着事务A再次进行查询,执行之前的SQL语句,会查询出12条数据。同样的SQL语句,查询出的数据不一样,这就是幻读。
简单来说:幻读就是一个事务中用一样的SQL多次查询,结果每次查询会有一些之前没有看到过的数据。(幻读特指查询到之前没有看到过的数据)
总结:
- 脏写、脏读、不可重复读、幻读都是由于数据库的多事务并发导致的问题。
- 脏写:是两个事务没有提交状态下修改同一个值。
脏读:是一个事务修改了数据的值,还没提交,被另一个事务读取到了,然后发生回滚,导致再次读取,就读取不到了。 - 不可重复读:针对的是已经提交的事务修改的值,被事务读取到了,事务内多次查询,多次读取的是别的已经提交的事务修改后的值。
- 幻读:就是一个事务中用一样的SQL多次查询,结果每次查询会有一些之前没有看到过的数据。