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