线上BUG:MySQL死锁分析实战
原文链接:线上BUG:MySQL死锁分析实战
1 线上告警
我们不需要关注截图中得其他信息,只要能看到打印得org.springframework.dao.DeadlockLoserDataAccessException
就足够了,就是MySQL发生死锁导致服务抛异常。
关于接口得逻辑,可以大概描述为:C端调用接口查询店铺得追踪事件列表,如果查询为空列表则顺便给初始化,这里的初始化是批量插入一批事件追踪列表,然后再返回,这里要给到一个关于表的信息点:这个表有主键索引和唯一索引。
1.1 云日志&死锁日志
不管是云日志还是死锁日志,都是显示着是并发重复插入导致的死锁,下面我就简单放一下云日志的截图,关于死锁的日志就不放了,因为下面将自己弄个demo来仿造并发重复请求导致批量插入发生死锁。
2 相关锁概念
2.1 INSERT语句如何加锁
首先我们得先知道在执行 INSERT 语句时,引擎(默认InnoDb)是如何加锁的。
默认情况下,执行 INSERT 语句是不用加锁的,
不过如果事务中执行一条 INSERT 语句,会先定位到该记录在 B+ 树的位置时,接着判断该位置的下一条记录被加了 grap 锁;如果是的话会在记录上加上一种类型为插入意向锁
的锁,最后事务进入等待状态。
插入意向锁是行级别的,也是一种间隙锁。插入意向锁之间互相兼容,多个事务可以同时对同一区间加上插入意向锁;还有在事务中,如果成功插入记录并且还未提交事务,那么当前事务还会持有插入记录的行锁。
2.2 键发生重复冲突
如果当插入记录时遇到重复键,当前事务会在生成错误信息前,对记录加上S锁,如果是唯一索引发生的重复键,会加上S型的next-key锁。
3 实践出真知
下面我们开始上例子了。
3.1 表信息
使用现有的表:
- 用户表
- 字段有:id、name、gender、user_type
- id为主键,name加了唯一索引;这里加唯一索引是要和上面的告警对齐。
CREATE TABLE `user` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`gender` char(1) NOT NULL,
`user_type` varchar(2) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
3.2 单元测试
接着是单元测试:
entity、mapper和service就直接省略过了。
单元测试主要是为了模拟线上的场景,前端并发发起请求,导致发生并发批量插入同一批数据。
3.2.1 代码如下
模拟并发数3:
@SpringBootTest(classes = MysqlInActionApplication.class)
@RunWith(SpringRunner.class)
public class MysqlInActionApplicationTests {
@Autowired
private UserService userService;
/**
* 线程数
*/
private static final int threadNum = 3;
/**
* 控制同时请求
*/
private static final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
/**
* 用户请求
*/
class UserRequest implements Runnable{
@Override
public void run() {
try {
// 等待
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 批量插入用户,插入数据不变
saveUserList();
}
}
@Test
public void contextLoads() {
for (int i = 0; i < threadNum; i++) {
new Thread(new UserRequest()).start();
// 计数减一
countDownLatch.countDown();
}
try {
Thread.currentThread().join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 批量插入用户
*/
public void saveUserList(){
List<User> userList = new ArrayList<>();
userList.add(new User().setName("winfun").setGender("m").setUserType("1"));
userList.add(new User().setName("fenghao").setGender("w").setUserType("2"));
userList.add(new User().setName("luff").setGender("m").setUserType("1"));
this.userService.saveBatch(userList);
}
}
3.2.2 运行结果
我们可以看到,基本和上面的告警信息是保持一致的了,直接抛出死锁的异常。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EVmS0aAc-1625403761274)(https://note.youdao.com/yws/res/65362/7B60F6EC0A50432399FA27974E08DDFC)]
3.3 MySQL 日志
我们再看看mysql的死锁日志:
show engine innodb status;
------------------------
LATEST DETECTED DEADLOCK
------------------------
2021-07-03 12:36:02 0x7000082df000
*** (1) TRANSACTION:
TRANSACTION 25374, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
MySQL thread id 17, OS thread handle 123145438982144, query id 356 localhost 127.0.0.1 root update
INSERT INTO user ( gender,
name,
user_type ) VALUES ( 'm',
'winfun',
'1' )
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 62 page no 4 n bits 72 index uk_name of table `test`.`user` trx id 25374 lock mode S waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 6; hex 77696e66756e; asc winfun;;
1: len 8; hex 0000000000000001; asc ;;
*** (2) TRANSACTION:
TRANSACTION 25373, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 2
MySQL thread id 19, OS thread handle 123145439539200, query id 369 localhost 127.0.0.1 root update
INSERT INTO user ( gender,
name,
user_type ) VALUES ( 'w',
'fenghao',
'2' )
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 62 page no 4 n bits 72 index uk_name of table `test`.`user` trx id 25373 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 6; hex 77696e66756e; asc winfun;;
1: len 8; hex 0000000000000001; asc ;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 62 page no 4 n bits 72 index uk_name of table `test`.`user` trx id 25373 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 6; hex 77696e66756e; asc winfun;;
1: len 8; hex 0000000000000001; asc ;;
*** WE ROLL BACK TRANSACTION (1)
3.3.1 事务一信息
*** (1) TRANSACTION:
TRANSACTION 25374, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
MySQL thread id 17, OS thread handle 123145438982144, query id 356 localhost 127.0.0.1 root update
INSERT INTO user ( gender,
name,
user_type ) VALUES ( 'm',
'winfun',
'1' )
事务一的trascationId为25374,存活0秒
事务一执行的SQL为:INSERT INTO user ( gender,name,user_type ) VALUES ( 'm','winfun','1' )
语句
3.3.2 事务一正在等待的锁
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 62 page no 4 n bits 72 index uk_name of table `test`.`user` trx id 25374 lock mode S waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 6; hex 77696e66756e; asc winfun;;
1: len 8; hex 0000000000000001; asc ;;
事务一正在等待插入记录的S型的next-key锁。
3.3.3 事务二的信息
*** (2) TRANSACTION:
TRANSACTION 25373, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 2
MySQL thread id 19, OS thread handle 123145439539200, query id 369 localhost 127.0.0.1 root update
INSERT INTO user ( gender,
name,
user_type ) VALUES ( 'w',
'fenghao',
事务二的事务ID为25373,存活0秒
事务一执行的SQL为:INSERT INTO user ( gender,name,user_type ) VALUES ( 'w','fenghao','2' )
语句
3.3.4 事务二持有锁信息
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 62 page no 4 n bits 72 index uk_name of table `test`.`user` trx id 25373 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 6; hex 77696e66756e; asc winfun;;
1: len 8; hex 0000000000000001; asc ;;
事务二持有 name 为 winfun 这一行唯一二级索引的X锁,但不是gap锁。
3.3.5 事务二等待的锁
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 62 page no 4 n bits 72 index uk_name of table `test`.`user` trx id 25373 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 6; hex 77696e66756e; asc winfun;;
1: len 8; hex 0000000000000001; asc ;;
事务二在添加插入意向锁
时发现记录已经被加上X型的间隙锁,所以无法添加,只能等待锁释放。
最后的解决
*** WE ROLL BACK TRANSACTION (1)
InnoDb 回滚了事务一,从而让事务一接触
分析总结:
- 事务一和事务二是并发批量插入同一批数据
- 事务二先执行,成功插入
winfun
这条记录,然后对这条记录加上了行锁 - 接着事务一进来了,发现
winfun
这个key是重复冲突了,接着在返回报错信息前,对winfun
这条记录加上S型的next-key锁,但是发现winfun
这条记录上已经有一个行锁,所以只能等待 - 接着事务二进行第二条记录的插入,即插入
fenghao
;此时发现它的下一条记录,即winfun
记录处已经有事务一要加入next-key锁,导致产生冲突,所以事务二也进入等待 - 最后,只能回滚事务一,从而让事务二完整执行下去。
最后
最后如何解决线上这个问题呢?
其实很简单,可以上分布式锁,但是我们这场景没有必要,反而会一定程度上增加接口的耗时;并且我们这个是C端接口,完全没有必要拥有初始化店铺数据的能力,把这能力保留在Admin端的接口即可;所以最后将初始化,即批量插入初始化数据的逻辑干掉即可~
1 线上告警
我们不需要关注截图中得其他信息,只要能看到打印得org.springframework.dao.DeadlockLoserDataAccessException就足够了,就是MySQL发生死锁导致服务抛异常。
在这里插入图片描述
关于接口得逻辑,可以大概描述为:C端调用接口查询店铺得追踪事件列表,如果查询为空列表则顺便给初始化,这里的初始化是批量插入一批事件追踪列表,然后再返回,这里要给到一个关于表的信息点:这个表有主键索引和唯一索引。
1.1 云日志&死锁日志
不管是云日志还是死锁日志,都是显示着是并发重复插入导致的死锁,下面我就简单放一下云日志的截图,关于死锁的日志就不放了,因为下面将自己弄个demo来仿造并发重复请求导致批量插入发生死锁。
在这里插入图片描述
2 相关锁概念
2.1 INSERT语句如何加锁
首先我们得先知道在执行 INSERT 语句时,引擎(默认InnoDb)是如何加锁的。
默认情况下,执行 INSERT 语句是不用加锁的,
不过如果事务中执行一条 INSERT 语句,会先定位到该记录在 B+ 树的位置时,接着判断该位置的下一条记录被加了 grap 锁;如果是的话会在记录上加上一种类型为插入意向锁的锁,最后事务进入等待状态。
插入意向锁是行级别的,也是一种间隙锁。插入意向锁之间互相兼容,多个事务可以同时对同一区间加上插入意向锁;还有在事务中,如果成功插入记录并且还未提交事务,那么当前事务还会持有插入记录的行锁。
2.2 键发生重复冲突
如果当插入记录时遇到重复键,当前事务会在生成错误信息前,对记录加上S锁,如果是唯一索引发生的重复键,会加上S型的next-key锁。
3 实践出真知
下面我们开始上例子了。
3.1 表信息
使用现有的表:
用户表
字段有:id、name、gender、user_type
id为主键,name加了唯一索引;这里加唯一索引是要和上面的告警对齐。
CREATE TABLE user
(
id
bigint(20) unsigned NOT NULL AUTO_INCREMENT,
name
varchar(20) NOT NULL,
gender
char(1) NOT NULL,
user_type
varchar(2) NOT NULL,
PRIMARY KEY (id
),
UNIQUE KEY uk_name
(name
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
3.2 单元测试
接着是单元测试:
entity、mapper和service就直接省略过了。
单元测试主要是为了模拟线上的场景,前端并发发起请求,导致发生并发批量插入同一批数据。
3.2.1 代码如下
模拟并发数3:
@SpringBootTest(classes = MysqlInActionApplication.class)
@RunWith(SpringRunner.class)
public class MysqlInActionApplicationTests {
@Autowired
private UserService userService;
/**
* 线程数
*/
private static final int threadNum = 3;
/**
* 控制同时请求
*/
private static final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
/**
* 用户请求
*/
class UserRequest implements Runnable{
@Override
public void run() {
try {
// 等待
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 批量插入用户,插入数据不变
saveUserList();
}
}
@Test
public void contextLoads() {
for (int i = 0; i < threadNum; i++) {
new Thread(new UserRequest()).start();
// 计数减一
countDownLatch.countDown();
}
try {
Thread.currentThread().join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 批量插入用户
*/
public void saveUserList(){
List<User> userList = new ArrayList<>();
userList.add(new User().setName("winfun").setGender("m").setUserType("1"));
userList.add(new User().setName("fenghao").setGender("w").setUserType("2"));
userList.add(new User().setName("luff").setGender("m").setUserType("1"));
this.userService.saveBatch(userList);
}
}
3.2.2 运行结果
我们可以看到,基本和上面的告警信息是保持一致的了,直接抛出死锁的异常。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EVmS0aAc-1625403761274)(https://note.youdao.com/yws/res/65362/7B60F6EC0A50432399FA27974E08DDFC)]
3.3 MySQL 日志
我们再看看mysql的死锁日志:
show engine innodb status;
LATEST DETECTED DEADLOCK
2021-07-03 12:36:02 0x7000082df000
*** (1) TRANSACTION:
TRANSACTION 25374, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
MySQL thread id 17, OS thread handle 123145438982144, query id 356 localhost 127.0.0.1 root update
INSERT INTO user ( gender,
name,
user_type ) VALUES ( 'm',
'winfun',
'1' )
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 62 page no 4 n bits 72 index uk_name of table test
.user
trx id 25374 lock mode S waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 6; hex 77696e66756e; asc winfun;;
1: len 8; hex 0000000000000001; asc ;;
*** (2) TRANSACTION:
TRANSACTION 25373, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 2
MySQL thread id 19, OS thread handle 123145439539200, query id 369 localhost 127.0.0.1 root update
INSERT INTO user ( gender,
name,
user_type ) VALUES ( 'w',
'fenghao',
'2' )
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 62 page no 4 n bits 72 index uk_name of table test
.user
trx id 25373 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 6; hex 77696e66756e; asc winfun;;
1: len 8; hex 0000000000000001; asc ;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 62 page no 4 n bits 72 index uk_name of table test
.user
trx id 25373 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 6; hex 77696e66756e; asc winfun;;
1: len 8; hex 0000000000000001; asc ;;
*** WE ROLL BACK TRANSACTION (1)
3.3.1 事务一信息
*** (1) TRANSACTION:
TRANSACTION 25374, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
MySQL thread id 17, OS thread handle 123145438982144, query id 356 localhost 127.0.0.1 root update
INSERT INTO user ( gender,
name,
user_type ) VALUES ( 'm',
'winfun',
'1' )
事务一的trascationId为25374,存活0秒
事务一执行的SQL为:INSERT INTO user ( gender,name,user_type ) VALUES ( 'm','winfun','1' )语句
3.3.2 事务一正在等待的锁
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 62 page no 4 n bits 72 index uk_name of table test
.user
trx id 25374 lock mode S waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 6; hex 77696e66756e; asc winfun;;
1: len 8; hex 0000000000000001; asc ;;
事务一正在等待插入记录的S型的next-key锁。
3.3.3 事务二的信息
*** (2) TRANSACTION:
TRANSACTION 25373, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 2
MySQL thread id 19, OS thread handle 123145439539200, query id 369 localhost 127.0.0.1 root update
INSERT INTO user ( gender,
name,
user_type ) VALUES ( 'w',
'fenghao',
事务二的事务ID为25373,存活0秒
事务一执行的SQL为:INSERT INTO user ( gender,name,user_type ) VALUES ( 'w','fenghao','2' )语句
3.3.4 事务二持有锁信息
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 62 page no 4 n bits 72 index uk_name of table test
.user
trx id 25373 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 6; hex 77696e66756e; asc winfun;;
1: len 8; hex 0000000000000001; asc ;;
事务二持有 name 为 winfun 这一行唯一二级索引的X锁,但不是gap锁。
3.3.5 事务二等待的锁
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 62 page no 4 n bits 72 index uk_name of table test
.user
trx id 25373 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 6; hex 77696e66756e; asc winfun;;
1: len 8; hex 0000000000000001; asc ;;
事务二在添加插入意向锁时发现记录已经被加上X型的间隙锁,所以无法添加,只能等待锁释放。
最后的解决
*** WE ROLL BACK TRANSACTION (1)
InnoDb 回滚了事务一,从而让事务一接触
分析总结:
事务一和事务二是并发批量插入同一批数据
事务二先执行,成功插入 winfun这条记录,然后对这条记录加上了行锁
接着事务一进来了,发现winfun这个key是重复冲突了,接着在返回报错信息前,对winfun这条记录加上S型的next-key锁,但是发现winfun这条记录上已经有一个行锁,所以只能等待
接着事务二进行第二条记录的插入,即插入fenghao;此时发现它的下一条记录,即winfun记录处已经有事务一要加入next-key锁,导致产生冲突,所以事务二也进入等待
最后,只能回滚事务一,从而让事务二完整执行下去。
最后
最后如何解决线上这个问题呢?
其实很简单,可以上分布式锁,但是我们这场景没有必要,反而会一定程度上增加接口的耗时;并且我们这个是C端接口,完全没有必要拥有初始化店铺数据的能力,把这能力保留在Admin端的接口即可;所以最后将初始化,即批量插入初始化数据的逻辑干掉即可~
Markdown 6816 字数 270 行数 当前行 1, 当前列 0HTML 6022 字数 191 段落
线上BUG:MySQL死锁分析实战的更多相关文章
- 线上bug分析
昨天下午大神把组内几十号人召集在一起开Online bug分析大会,主要是针对近期线上事故从事故原因和解决方案两个维度来分析. 对金融软件来说,每一次的线上事故都有可能给公司带来重大的损失,少扣了用户 ...
- 出现线上bug,测试人能做些什么?
测试奇谭,BUG不见. 大家好,我是谭叔. 一提到线上问题,很多测试小白要么"原则性"恐惧,要么憨憨如也,不知如何下手. 本篇文章,我再细化下这道常见的面试题,跟大家捋捋发生线上问 ...
- 程序员如何描述清楚线上bug
案例 一个管理后台的bug,把操作记录中的操作员姓名,写成了该操作员的id.原因是修改了一个返回操作人姓名的函数,返回了操作人的id.但是还有其他地方也用这个函数,导致其他地方把姓名字段填写成了操作员 ...
- 记一次线上bug排查-quartz线程调度相关
记一次线上bug排查,与各位共同探讨. 概述:使用quartz做的定时任务,正式生产环境有个任务延迟了1小时之久才触发.在这一小时里各种排查找不出问题,直到延迟时间结束了,该任务才珊珊触发.原因主要就 ...
- 听说”双11”是这么解决线上bug的
听说"双11"是这么解决线上bug的 --Android线上热修复的使用与原理 预备知识和开发环境 Android NDK编程 AndFix浅析 Android线上热修复的原理大同 ...
- 关于线上bug
之所以想写下线上bug,因为发觉有些公司对线上bug的处理是比较严格甚至是很苛刻,涉及到的相关人可能会因此而背黑锅. 之所以会存在这样情况,因为公司各部门都有关联,特别是用户.老板的投诉,也给公司会造 ...
- 「日常开发」记一次因使用Date引起的线上BUG处理
生活中,我们需要掌控自己的时间,减少加班,提高效率:日常开发中,我们需要操作时间API,保证效率.安全.稳定.现在都2020年了,了解如何在JDK8及以后的版本中更好地操控时间就很有必要,尤其是一次线 ...
- 线上bug的解决方案--带来的全新架构设计
缘由 本人从事游戏开发很多年一直都是游戏服务器端开发. 因为个人原因吧,一直在小型公司,或者叫创业型团队工作吧.这样的环境下不得不逼迫我需要什么都会,什么做. 但是自我感觉好像什么都不精通..... ...
- 线上bug或故障界定及填写规范
[线上故障与线上Bug界定] 一.线上故障: 1. 故障参照公司规范稍做调整: a) 1级故障:资讯首页或主App首页无法打开:多条业务线同时不可用:超过15分钟: b) ...
随机推荐
- 解密华为云FusionInsight MRS新特性:一架构三湖
摘要:华为云安全网关产品总监郭冕在"华为云TechWave云原生2.0专题日"上发表<华为云FusionInsight MRS,一个架构实现三种数据湖>的主题演讲,分享 ...
- [BD] Sqoop
什么是Sqoop 数据交换工具(ETL):RDBMS(MySQL.Oracle等)<-->Sqoop<-->HDFS(HBase.Hive等) 基于JDBC 执行数据交换时,本 ...
- Win10屏幕亮度不能调节,调节无效怎么办?
Win10屏幕亮度不能调节,调节无效怎么办? 听语音 浏览:1027 | 更新:2019-11-22 11:43 1 2 3 4 5 6 7 分步阅读 一些用户在使用win10系统之后,出现了电脑屏幕 ...
- Linux如何查看文件的创建、修改时间?
Linux如何查看文件的创建.修改时间? 利用stat指令查看文件信息 三种时间的介绍 ATime --文件的最近访问时间 只要读取时间,ATime就会更新 MTime --文件的内容最近修改的时间 ...
- SpringBoot 上传文件如何获取项目工程路径
上传文件时,需要将上传的文件存放于工程路径中,以便前端能够获取文件资源,那如何获取工程路径呢? //获取 SpringBoot 工程中 static 的绝对路径 String serverpath= ...
- STM32 库函数 延时函数计算
- 国外DIY网站
https://site.douban.com/109950/widget/notes/250072/note/248811721/
- Keil编译后的Code,RO,RW,ZI分别表示什么以及和芯片Flash、SRAM的对应关系
在使用keil开发STM32应用程序时,点击Build后在Build Output窗口中经常会有如下信息:<ignore_js_op> 以前一直好奇这几个参数和实际使用的STM32芯片中F ...
- Archlinux常用软件推荐 更新于2021年4月
记录一下常用软件 必装软件 包管理工具 yay 代替pacman的包管理 yaourt 备用 终端工具 zsh oh-my-zsh-git 搭配zsh利器` proxychains4 终端代理工具` ...
- Element-ui Popconfirm气泡确认框的确认及取消事件不生效
Element-ui 官方文档对 Popconfirm气泡确认框的一些属性及事件的描述不够详细,导致第一次使用时会遇到各种各样的问题 对确定事件及取消事件描述如下: 但是如果给组件绑定@confirm ...