IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> 排查生产环境:MySQLTransactionRollbackException数据库死锁 -> 正文阅读

[大数据]排查生产环境:MySQLTransactionRollbackException数据库死锁

排查生产环境数据库死锁问题记录。

一. 问题现状

程序直接宕机,并在error.log日志中发现大量的报错日志,如下:

### Error updating database.  
Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: 
Lock wait timeout exceeded; try restarting transaction
### The error may exist in com/xxx/dao/mapper/xxxMapper.java (best guess)
### The error may involve com.xxx.dao.mapper.xxxMapper.update-Inline
### SQL: UPDATE xxx_table SET a1=?, a2=?, a3=?, a4=?, a5=?  WHERE (q1= ?)

程序中使用的是mybatis-plus,报错信息准确提示出了对应的mapper文件以及对应的sql操作是updateById语句。再结合其他日志信息,初步判断出了对应的位置以及症结可能与事务操作有关。

二. 可能原因

  1. 事务过程中执行其他非数据库操作,导致事务长期未被处理
  2. 事务未被正常处理
  3. 网段导致应用端请求未被正常发送给数据库,数据库等待应用后续操作
  4. 应用服务器性能问题,CPU爆满等导致应用无法及时切换到该进程进行处理

根据以上原因,首先排查服务器情况发现不存在cpu爆满的情况,后续继续追究MySQL事务方面的问题。
从MySQL入手,执行以下语句查看锁情况:

-- 查看正在锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS; 
-- 查看等待锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

(由于笔者的线上环境执行了重启,所以这会儿已经看不到具体结果了,实际上应该能看到对应等待锁的事务)
但是可以模拟线上情况,比如在代码中的@Transaction代码中打个debug断点,然后观察情况:

-- 查看执行进程
SHOW PROCESSLIST;
-- 执行查询未提交事务语句
SELECT * from  information_schema.INNODB_TRX;

基本可以确定问题出现在代码层。

三. 排查代码

生产伪代码如下:

@Transactional(rollbackFor = Exception.class)
public synchronized boolean doSomething(Object o){
	// ...
	if(flag){
		aMapper.updateById(o);
	}
	// ...
	bMapper.insert(o);
	return true;

}

这里的目的是做一个事务,aMapper需要根据某个条件判断更新操作,bMapper则需要根据业务流程做新增操作,同时为了考虑并发情况下的操作所以在方法名上加了synchronized 同步锁。咋一看感觉没什么问题,但是在service层的方法上同时使用事务和锁无法保证同步。

原理是:@Transactional是使用了Sping AOP 实现的;Synchronized只是锁当前代码块,当执行完Synchronized包含的代码块就已经执行完了;此时@Transactional还未提交,所以就造成上一个事务没有提交,而下一个请求过来了争夺数据库的事务锁,在大量请求的情况下,有可能会承受不住。

针对这种情况,我们得保证让Synchronized的锁范围比@Transactional作用范围大,于是可以做如下改造:

public synchronized boolean doSomething(Object o){
	return transactionDoSomething(o)
}

@Transactional(rollbackFor = Exception.class)
public boolean transactionDoSomething(Object o){
	// ...
	if(flag){
		aMapper.updateById(o);
	}
	// ...
	bMapper.insert(o);
}

另外一种方案:


@Transactional(rollbackFor = Exception.class)
public boolean transactionDoSomething(Object o){
	synchronized(this){
		// doSomething
		return true;
	}
}

三. 扩展(ReentrantLock/druid自动重连数据库)

3.1 根因分析

经过以上处理,实际上应该不会再出现数据库死锁的情况了,但是经过日志的进一步查看发现没有将@Transaction与synchronized一起使用的地方也出现过死锁情况,在此处仅仅使用了synchronized同步锁,唯一与以上情况相似的就是同样使用了updateById。

public synchronized void doSomething(Object o){
	// ...
	xxxMapper.updateById(o);
}

剩下就是还有一种可能,网络波动、防火墙或是其他原因,导致了应用和mysql的连接断开,这个时候进入到这个代码块的执行逻辑在使用已经失败的连接,又由于这是加了synchronized的同步代码块,第一个进来的请求一直在等待连接导致后续进来的请求全都被堵塞。

3.2 改造synchronized使用ReentrantLock

于是我们在此处尽量引入一种比较友好的锁,ReentrantLock
ReentrantLock的tryLock方法支持配置最大同步等待时间,由此来避免以上堵塞情况,写法如下:

@Service
public class LockService{
	// 初始化ReentrantLock
	private final Lock reentrantLock = new ReentrantLock();
	// 设置最大锁等待时间
    private final long tryLockTime = 2L;

	public void addRound(Long manMachineId,LocalDateTime time) {
        try {
            if (reentrantLock.tryLock(tryLockTime, TimeUnit.SECONDS)) {
            	System.out.println("获取到锁,开始做一些事...");
            }else {
            	System.out.println("等待2s后获取锁失败");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
        	// 最终需要释放锁
			reentrantLock.unlock();
		}
    }
}

3.3 加入druid自动重连数据库

程序中使用了druid,所以我们加入以下配置信息即可实现在数据库停止的时候等待数据库恢复并自动重连,配置如下:

# 标记当Statement或连接被泄露时是否打印程序的stack traces日志
spring.datasource.druid.log-abandoned=true
# 是否自动回收超时连接
spring.datasource.druid.remove-abandoned=true
# 超时时间(以秒数为单位)
spring.datasource.druid.remove-abandoned-timeout=10

四. 总结

至此,本次MySQL线上死锁问题就已结束排查。
由于线上问题一般都比较复杂或者比较难复现,所以排查线上问题首先需要分析日志,这个时候就要求我们程序的日志要尽可能做到完善。
然后就是大胆猜测,小心验证,其中不免会经历多次推到重来的历程。此后该问题再次出现就不会再成为你的问题。

参考资料:

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-08-06 10:50:33  更:2022-08-06 10:53:32 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 4:08:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码