详解脏读、不可重复读、幻读、数据库隔离级别

2022-01-18 11:44:36  晓掌柜  版权声明:本文为站长原创文章,转载请写明出处


一、前言

    我们经常在并发编程或者面试题中看到脏读、不可重复读、幻读、数据库隔离级别、事务等字眼,这一部分是在高并发编程和面试时的一个重要内容,同时也是在多线程及网络并发时需要着重注意的事项,今天我们就上述几点做一个详述,GO!

二、相关说明

    ① 我们在讲述时会假定两个线程,并各持有一个事务。

    ② 事务A做数据读取操作,事务B做数据修改操作。

    ③ 为了更好的控制各个事务的事件节点,我们使用线程休眠。

    ④ 我们会展示在程序开始到结束,每个场景下的数据变更及影响

    ⑤ 数据方面我们以0和1做为简单区分,默认为0

三、脏读

    3.1、什么是脏读

    现有事务A和事务B,原始数据为0。事务A对数据进行了修改,在修改操作没有完全结束时(修改的同时还有后续的业务逻辑处理),事务B读取了修改后的数据1,同时事务A又抛出了异常导致数据回滚为0。这时事务B读取的数据是脏数据,即为脏读。

    3.2、脏读的流程示意

       

    3.3、代码模拟


/*
* 功能描述: 脏读
* Param: []
* Return: void
*/
private void dirtyRead () throws InterruptedException {
System.out.println("测试开始,当前数据为:" + i);

System.out.print("模拟事务A进行数据修改操作,");
writeWithRollBack writeWithRollBack = new writeWithRollBack();
Thread threadWrite = new Thread(writeWithRollBack);
threadWrite.start();

Thread.sleep(3000);

System.out.print("模拟事务B进行读取操作,");
read read = new read();
Thread threadRead = new Thread(read);
threadRead.start();

Thread.sleep(6000);
System.out.println("最终的数据为:" + i);
}

/*/*
* 功能描述: 模拟线程数据修改然后回滚
* Param:
* Return:
*/
private static class writeWithRollBack implements Runnable{

@Override
@Transactional(rollbackFor = Exception.class)
public void run() {
try {
i++;
System.out.println("此时已经修改数据为:" + i);
/* 线程休眠5秒钟 */
Thread.sleep(5000);
/* 模拟一个异常,事务回滚 */
i--;
System.out.println("事务异常回滚了,当前数据为:" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

/*
* 功能描述: 模拟线程读取数据
* Param:
* Return:
*/
static class read implements Runnable{

@Override
public void run() {
System.out.println("当前线程读取到并持有的数据为:" + i);
}
}

       

四、不可重复读

    4.1、什么是不可重复读

    事务B读取了两次数据,当时在读取的途中事务A做了一次修改,导致事务B两次读取的数据不一致,即不可重复读。

    PS: 不同事务,对同一数据进行操作,也可能会导致不可重复读问题

    4.2、不可重复读流程示意

       

    4.3、代码模拟


/*
* 功能描述: 不可重复读
* Param: []
* Return: void
*/
private void nonRepeatableRead () {
System.out.println("测试开始,当前数据为:" + i);
System.out.print("事务A读取了数据,");
readAgain readAgain = new readAgain();
Thread threadRead = new Thread(readAgain);
threadRead.start();

System.out.println("此时事务B对数据进行修改,");
write write = new write();
Thread threadWrite = new Thread(write);
threadWrite.start();

}

/*
* 功能描述: 事务两次读取
* Param:
* Return:
*/
private static class readAgain implements Runnable {

@Override
public void run() {
try {
System.out.println("事务A第一次读取,当前数据为:" + i);
Thread.sleep(3000);
System.out.println("事务A第二次读取,当前数据为:" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

/*
* 功能描述: 模拟线程对数据进行修改
* Param:
* Return:
*/
private static class write implements Runnable {

@Override
public void run() {
i++;
System.out.println("此时数据已经修改为:" + i);
}
}

       

五、幻读

    5.1、什么是幻读

    事务A读取了两次数据,但是在两次读取之间事务B对数据进行了增减操作,导致事务A两次读取的数据集合不一致。幻读同不可重复读类似,但是更侧重的是集合数据的不同。

    5.2、幻读流程示意

       

    5.3、代码示例


/*
* 功能描述: 幻读
* Param: []
* Return: void
*/
private void phantomRead() {
integerList.add(0);
System.out.println("测试开始,当前数据为:" + integerList);
System.out.print("事务A读取了数据,");
readListAgain readListAgain = new readListAgain();
Thread threadRead = new Thread(readListAgain);
threadRead.start();

System.out.println("此时事务B对集合进行修改,");
writeList writeList = new writeList();
Thread threadWrite = new Thread(writeList);
threadWrite.start();
}

/*
* 功能描述: 两次读取集合,用以模拟幻读
* Param:
* Return:
*/
private static class readListAgain implements Runnable{

@Override
public void run() {

try {
System.out.println("事务A第一次读取集合:" + integerList);
Thread.sleep(2000);
System.out.println("事务A第二次读取集合:" + integerList);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

/*
* 功能描述: 模拟线程对集合进行修改
* Param:
* Return:
*/
private static class writeList implements Runnable{

@Override
public void run() {
integerList.add(1);
System.out.println("此时集合已经被修改:" + integerList);
}
}

       

六、数据库隔离级别

    为了解决上述脏读、幻读、不可重复读的问题,主流的关系型数据库都定义了不同的数据库隔离级别,主要分为:

        ① 读未提交    ② 读已提交    ③ 可重复读    ④ 可串行换

    6.1、读未提交

        字面意思为:可以读未提交的事务结果,即所有的事务都可以读到其他未提交的执行结果。隔离级别最低,使用最少。

        优点:① 并发处理强    ② 系统开销低

        缺点:① 会引发脏读、幻读、不可重复读

        总结: 不建议使用

    6.2、读已提交

        字面意思为:可以读已经提交的事务结果,即所有的事务只能看到其他事务已经提交的执行结果。

        优点:① 可以防止脏读问题

        缺点:① 依然会产生幻读、不可重复读

        总结:大多数的关系型数据库默认级别

    6.3、可重复读

        字面意思为:可重复多次读取数据,因为在事务执行期间会锁定该事务以任何方式引用的所有行,所以产生的多次结果集一致。

        优点:① 可解决脏读、不可重复读的问题

        缺点: ① 仍会存在幻读的问题

        总结:MySql的默认隔离级别,使用多数场景

    6.4、可串行化

        字面意思为:对所有的事务进行串行化操作(给事务加上共享锁,可以读取但是不能修改)。是最高的数据库隔离级别。

        PS:共享锁(读写锁),读锁被占用的话,写锁就不能使用;读锁可以被多个事务占用。

        优点:① 可有效解决脏读、幻读、不可重复读问题

        缺点:① 可能导致大量的锁竞争    ② 易引发超时问题

        总结:不建议数据库使用


更多精彩请持续关注:guangmuhua.com

       

   



       


最新评论: