前言
距离第一期的同事的代码问题已经3个月了。主要还是公司的业务比较简单,加上平时遇到的问题也比较少。最近也算是发现了几个典型问题。
问题代码
重复提交(重放攻击)
1. 提交活动审批接口(web端接口)
新建活动之后,活动需要走审批流程,每次提交审批都会生成一条审批申请数据。这种情况小杨哥
就喜欢加锁,但是加锁只能解决并发提交问题,不能解决在不同时间段的重复请求问题。
问题代码
- 如果重复调用提交审批接口,
service.commitApprove()
就会生成多条审批数据,引发数据问题。
java
代码解读
复制代码
@Transactional(rollbackFor = Exception.class)
@Override
public void commit(String id) {
User user = UserUtil.getUser();
this.update(Wrappers.lambdaUpdate(OfflineActivityEntity.class)
.eq(OfflineActivityEntity::getId,req.getId())
//更新状态
.set(OfflineActivityEntity::getStatus, 2);
OfflineActivityEntity OfflineActivityEntity = activityMapper.selectById(id);
//提交审批数据(会生成一条审批数据)
service.commitApprove(OfflineActivityEntity, user);
}
调整逻辑
- 增加
update
条件,只有未提交审批的数据,才能提交审批。 - 获取更新状态,更新成功后才执行下一步,更新失败就返回。
调整后的代码:
less
代码解读
复制代码
@Transactional(rollbackFor = Exception.class)
@Override
public Boolean commit(String id) {
User user = UserUtil.getUser();
Boolean updateRet = this.update(Wrappers.lambdaUpdate(OfflineActivityEntity.class)
.eq(OfflineActivityEntity::getId,req.getId())
//未提交审批--------
.eq(OfflineActivityEntity::getStatus,1)
//更新状态
.set(OfflineActivityEntity::getStatus, 2);
OfflineActivityEntity OfflineActivityEntity = activityMapper.selectById(id);
//更新成功后再 提交审批数据(会生成一条审批数据)-------
if(updateRet){
service.commitApprove(OfflineActivityEntity, user);
}
return updateRet;
}
2.取消报名(H5接口)
报名业务算是项目中比较敏感的业务了,名额不能报超呀。看一下报名接口
加了一个分布式锁,嗯嗯没有问题。再一看取消报名
接口没有加锁,这下出问题了吧,找到同事小李,小李说取消报名就不加锁了,又不会报超。真的吗?????
问题代码:
- 假设我一直调用取消报名的接口,虽然对我没有影响,但是活动报名人数一直减。实际只减少一个报名人数,但是数据库却已经减了多次,报名的时候再进行人数判断的时候可能就会出问题了,引发报名人数超过限制的问题。。
- 更新活动人数的时候,有并发问题
set(Act::getNum, act.getNum() - 1).update()
,执行到update
的时候我第二行查询出来的act
的报名人数在数据库中已经更新了,所以这儿还有并发问题。
java
代码解读
复制代码
@Override
@Transactional
public void cancelActivityEnroll(ObjectReq req) {
User user = UserUtil.getUser();
Act act = actService.getById(req.getId());
if (act.getEndTime().isBefore(LocalDateTime.now()) && act.getStartTime().isBefore(LocalDateTime.now())) {
throw new JeecgBootException("活动已开始,无法进行取消报名操作");
}
//更新报名信息
this.lambdaUpdate().eq(ActPerson::getActId, req.getActId())
.eq(ActPerson::getUserId, user.getId())
.eq(ActPerson::getDel, false)
.set(ActPerson::getDel, true).update();
//更新活动人数
actService.lambdaUpdate().eq(Act::getId, req.getActId()).
set(Act::getNum, act.getNum() - 1).update();
}
调整逻辑
- 获取报名信息更新结果,更新成功后才执行更新活动人数。
- 更新活动人数,调整
sql
,更新时直接读取当前行的报名人数,而不是通过参数传入 。 (因为并发并不大,所以这儿也不必加分布式锁,update
语句自带有排他锁,改成如下也就没有并发问题了)
调整后的代码
java
代码解读
复制代码
@Override
@Transactional
public void cancelActivityEnroll(ObjectReq req) {
User user = UserUtil.getUser();
Act act = actService.getById(req.getId());
if (act.getEndTime().isBefore(LocalDateTime.now()) && act.getStartTime().isBefore(LocalDateTime.now())) {
throw new JeecgBootException("活动已开始,无法进行取消报名操作");
}
//更新报名信息
Boolean updateRet = this.lambdaUpdate().eq(ActPerson::getActId, req.getActId())
.eq(ActPerson::getUserId, user.getId())
.eq(ActPerson::getDel, false)
.set(ActPerson::getDel, true).update();
//更新活动人数 ------------------
if(updateRet){
actService.lambdaUpdate()
.setSql("set num -1").eq(Act::getId, req.getActId());
}
}
SQL问题
distinct胡乱使用
完了生成环境列表查询又报错了,咋眼一看又是小刘写的。。。这个sql就一个分页查询,业务数据就几千条呀,这都能SQL
查询超时。。。。。。。。。。
单独写过一篇文章:【distinct 别乱用】,文章对比了
distinc
、dictinct on
、group by
执行性能
问题SQL
- 把
distinct
当group by
使用 -
distinct
多余使用,sql
结构不清晰
sql
代码解读
复制代码
-- ua 为主表,uo为副表;distinct 后字段太多,去重效率低
SELECT DISTINCT ua."id",
ua..........(ua表27个字段) ,
uo."examine_star" AS current_star
-- 业务上已经限制,同一年只能有一条数据,这儿的distinct就是多余的
FROM (SELECT DISTINCT ON(dept_id) * FROM "user_apply" ua WHERE year = #{req.year}) ua
LEFT JOIN
"dept_org" do ON do.id = ua."dept_id"
LEFT JOIN
(SELECT * FROM public_notice pn WHERE status = 2 AND year = #{req.year}) pn ON pn.YEAR = ua."year"
LEFT JOIN
(SELECT * FROM "public_dept" WHERE state = 1) pd ON pd.public_id = pn.id AND pd.dept_id = ua."dept_id"
调整逻辑
- 用
group by
替换distinct
-
删除
无用的distinct
- 把子查询都去掉
调整后的SQl
sql
代码解读
复制代码
SELECT ua."id",ua..........(ua表27个字段) ,
uo."examine_star" AS current_star,
FROMFROM "user_apply" ua
LEFT JOIN "dept_org" do ON do.id = ua."dept_id"
LEFT JOIN public_notice pn pn ON pn.YEAR = ua."year"
LEFT JOIN"public_dept" pd ON pd.public_id = pn.id AND pd.dept_id = ua."dept_id"
where .........
group by ua."id",ua..........(ua表27个字段) uo."examine_star"
索引失效
“生产环境又报错了”,我c这个问题都出现好多次,就是SQL
条件中用了in
查询,之前大家一致认为是in
后面跟了几千个参数,参数太长了导致全表扫描(100w数据)。这次in
后面就几十个参数,为啥还能报错呢!!!
之前单独写了一篇文章【select in 的知识盲区】,文章分了
select in
不同写法,以及索引失效场景的执行计划和性能。
问题SQL
-
in
后面的参数是通过代码先查询出来再通过foreach
拼接,member
表中的这个字段为varchar
,所以就转型了。代码和sql问题不大,主要就是dept_id
本来就应该是数字类型bigint
sql
代码解读
复制代码
-- 字段和表名非实际字段
SELECT dept_id, count(id) AS total
FROM "member"
WHERE del = 0 AND status = 3
AND active_status = 1
AND dept_id IN (
222726,222546,222562,222727,222722,222694,222633,222859,222854,222622,222822,222552,222567,
2057 95,205794,205782,205782,222848,222830,222602,225806,222780,222823,222857,222670,222672,222779,
205692,222632,222722,222540,222680,222582,222682,205692,222858,222573,222656,222673,222677,
222579,205773,222577,222669,222683,222679,222668,222570,222655,222572,222602,222666,222592,222688,222590,222585,222672,222569,222756,205775,222829,205702,222674,222676,222675,222583,222654
,222729,222587,222548.......)
GROUP BY dept_id ;
调整逻辑
- 统一各表中的
dept_id
字段类型为bigint
-
in
参数太长了,在mybatis
拼接的时候也比较耗时间
调整后SQL
sql
代码解读
复制代码
SELECT dept_id, count(id) AS total
FROM "member"
WHERE del = 0 AND status = 3
AND active_status = 1
AND dept_id IN (
select id from dept where ..)
GROUP BY dept_id ;
总结
重复提交不仅前端要限制,后端做了才能真正防止重复提交。还需要考虑一些业务实际操作场景,加锁可不能防止用户双击造成的重复提交问题,当业务执行时间很短的时候,加锁也没用。实际开发中尽量保证SQL
逻辑结构的清晰,关键字就别乱用了。
如有侵权请联系站点删除!
技术合作服务热线,欢迎来电咨询!