MySql 事务隔离级别(READ_COMMITTED/REPEATABLE_READ)遇到问题后,相关测试

1:线上问题描述:

        1.1:线上使用MySql 默认的隔离级别:REPEATABLE_READ,

        1.2:线上有相关流程:当相关机构授信流程N 分钟没有回调时,直接触发其他机构授信问询机构, 触发下一家流程使用两种方式进行:一种是采用延时队列进行,一种是使用类似补偿机制:task 轮训触发.

      1.3:线上出现单子:同时由 延时队列以及task触发,并且时间间隔很短,可以认为是并发进行

       task 执行时间点:

                  开始执行时间:     2020-12-02 12:08:00.005
                 结束执行时间点: 2020-12-02 12:08:00.385   

    延时队列处理相关时间点:

                  开始执行时间: 2020-12-02 12:08:00.372  

                  结束执行时间点:2020-12-02 12:08:00.755       

由于在方法调用入口处:使用   @Transactional  注解开始事务, 由于由时间的重叠,导致出现并发事务问题: 

                 task 执行时,插入新的请求机构数据,在未提交事务以前, 延时队列开启新的事务,导致延时队列查询相关机构是否已经发送机构数据未查询到, 出现再次发送给同一家机构的问题

2: 由于Mysql 使用的事务隔离级别是:REPEATABLE_READ, 一个事务再插入数据过程中,未提交事务以前,再开启其他事务读取数据时,读取不到新增的数据.

事务流程图):

事务流程

A事务

B事务

task执行开始,并开启事务

2020-12-02 12:08:00.005

 
task insert相关数据: 如:机构码为:AAA 
 

延时队列执行并开启事务

2020-12-02 12:08:00.372 

task 执行完成 并commit事务

2020-12-02 12:08:00.385   

 
 

查询是否有机构码为:AAA的相关数据?

1:查询结果为无(注:都查询的主库,不存在主从延迟的问题)

则进行相关数据的插入

 

执行完成并commit 事务

2020-12-02 12:08:00.755   

  

 

自测相关代码:

package com.spring.transaction;/**
 * @author
 * @since 2020-12-11 16:47
 */

import java.text.SimpleDateFormat;
import java.util.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.*;
import org.springframework.transaction.annotation.*;
import org.springframework.transaction.support.*;

/**
 * @author guojl
 * @version 1.0.0
 * @ClassName Operation.java
 * @Description
 * @createTime 2020年12月11日 16:47:00
 */

@Transactional
@Service
public class OperationService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private DataSourceTransactionManager transactionManager;


    public void updateScore(String userName, int addScore) {
        String sql = "update t_user set user_name=?";
        //默认返回匹配行数,如果需要返回更新后行数,则需要在jdbc 连接中设置参数:useAffectedRows=true
        int updateNum = jdbcTemplate.update(sql, userName);
        System.out.println("updateScore=" + Thread.currentThread().getName() + "线程,更改userName=" + userName + ",更新行数:updateNum= " + updateNum);

    }

    public void updateScoreByCondition(String userName, String orgUserName) {
        String sql = "update t_user set user_name=? where  user_name=?";
        //默认返回匹配行数,如果需要返回更新后行数,则需要在jdbc 连接中设置参数:useAffectedRows=true
        int updateNum = jdbcTemplate.update(sql, userName, orgUserName);
        System.out.println("updateScore=" + Thread.currentThread().getName() + "线程,更改userName=" +
                userName + "更改条件:原userName=" + orgUserName + ",更新行数:updateNum= " + updateNum);

    }

    public void addScore() {
        String insertSql = "insert into t_user(user_name,score) values('张三',5)";
        jdbcTemplate.execute(insertSql);
    }

    public void addScore(String user_name) {
        String insertSql = "insert into t_user(user_name,score) values('" + user_name + "',5)";
        jdbcTemplate.execute(insertSql);
        System.out.println("addScore=" + Thread.currentThread().getName() + "线程: " + user_name);

    }


    public int queryScore() {
        String sql = "select * from  t_user";
        //默认返回匹配行数,如果需要返回更新后行数,则需要在jdbc 连接中设置参数:useAffectedRows=true
        List<Map<String, Object>> sqlRowSet = jdbcTemplate.queryForList(sql);
        System.out.println("queryScore=" + Thread.currentThread().getName() + "查询结果:" + sqlRowSet);


        boolean isActiveFlag = TransactionSynchronizationManager.isActualTransactionActive();
//        System.out.println("queryScore=" + Thread.currentThread().getName() + "是否事务开启:" + isActiveFlag);

        return sqlRowSet.size();
    }


    //测试事务生效
    public void mulitOperation() {

        boolean isActiveFlag = TransactionSynchronizationManager.isActualTransactionActive();
        System.out.println(Thread.currentThread().getName() + "线程:" + "isActiveFlag=" + isActiveFlag);
        int size = queryScore();
        addScore();
        queryScore();

        if (1 == 1) {
            throw new RuntimeException("throw Exception!!!");
        }
    }


    //测试事务生效
    @Transactional
    public void mulitOperation1() throws InterruptedException {
        boolean isActiveFlag = TransactionSynchronizationManager.isActualTransactionActive();
        System.out.println(Thread.currentThread().getName() + "线程:" + "是否开启事务isActiveFlag=" + isActiveFlag);

        queryScore();

        new Thread(new Runnable() {
            @Override
            public void run() {
                //1.获取事务定义
                DefaultTransactionDefinition def = new DefaultTransactionDefinition();
                //2.设置事务隔离级别,开启新事务
                def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
                //隔离级别设置
//                def.setIsolationLevel(Isolation.READ_COMMITTED.value());
                def.setIsolationLevel(Isolation.REPEATABLE_READ.value());
                //3.获得事务状态
                TransactionStatus status = transactionManager.getTransaction(def);


                try {
                    //TODO 确定是否开启了事务
                    boolean isActiveFlag = TransactionSynchronizationManager.isActualTransactionActive();
                    System.out.println(Thread.currentThread().getName() + "线程:" + "事务是否开启=" + isActiveFlag);
                    //如果开启了事务,第一次查询不到新增的数据
                    queryScore();
//                AopContext.currentProxy()


                    try {
                        Thread.currentThread().sleep(Long.parseLong("10000"));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //间隔10秒钟, 主线程中的addScore() 已经执行,但是事务还没有提交
//                如果开启了事务,并且事务的隔离级别是:可重复读,则读取不到主线程的新增数据
                    queryScore();

                    try {
                        Thread.currentThread().sleep(Long.parseLong("15000"));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //再次间隔15秒,此时主线程的addScore() 相关事务已经提交
//                如果开启了事务,并且事务的隔离级别是:可重复读,则读取不到主线程的新增数据
                    queryScore();

                    updateScoreByCondition("子线程更改原数据库存在记录","存在数据测试");
                    System.out.println("子线程更新数据库已经存在的记录后查询");
                    queryScore();

                    updateScoreByCondition("子线程更改原数据库插入记录","insert测试");
                    System.out.println("子线程更新主线程插入的数据记录后查询");
                    queryScore();



                } finally {
                    transactionManager.commit(status);
                }


            }
        }).start();

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        String dateTimeStr = simpleDateFormat.format(new Date());
        addScore("insert测试");
        //查询
        queryScore();
//        updateScore("事务测试NEW1112", 1);
        Thread.currentThread().sleep(Long.parseLong("20000"));

        System.out.println(Thread.currentThread().getName() + "线程: execute finish!!!");

    }

    public void async() {


    }


}

执行相关代码以前:相关数据库表数据:

  

执行相关代码流程后,控制台输出:

数据库结果:

综上查看: 

    当一个线程A开始事务处理流程中,如果事务的级别是: RR(REPEATABLE_READ)时,

另一个线程B开启事务, A 线程插入数据并提交后,B线程多次读取,是query不到A线程insert 并提交后相关数据的,但是可以更新到另一个线程insert并commit后的数据

这里:数据库事务隔离级别(RR), 但是在A线程提交后,B线程没有查询到A事务insert并commit后的数据:

相关问题参考:https://www.jb51.net/article/158088.htm

如果事务级别改为:READ_COMMITTED ,执行结果:读取到A事务insert 以及 update后的数据

 

附加: MySql 客户端命令测试事务相关:https://blog.csdn.net/czh500/article/details/105009219

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值