最近发现同事的代码问题(第二篇)

最近发现同事的代码问题(第二篇)

技术博客 admin 121 浏览

前言

距离第一期的同事的代码问题已经3个月了。主要还是公司的业务比较简单,加上平时遇到的问题也比较少。最近也算是发现了几个典型问题。

问题代码

重复提交(重放攻击)

1. 提交活动审批接口(web端接口)

新建活动之后,活动需要走审批流程,每次提交审批都会生成一条审批申请数据。这种情况小杨哥就喜欢加锁,但是加锁只能解决并发提交问题,不能解决在不同时间段的重复请求问题。

问题代码
  1. 如果重复调用提交审批接口,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); }
调整逻辑
  1. 增加update条件,只有未提交审批的数据,才能提交审批。
  2. 获取更新状态,更新成功后才执行下一步,更新失败就返回。

调整后的代码:

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接口)

报名业务算是项目中比较敏感的业务了,名额不能报超呀。看一下报名接口加了一个分布式锁,嗯嗯没有问题。再一看取消报名接口没有加锁,这下出问题了吧,找到同事小李,小李说取消报名就不加锁了,又不会报超。真的吗?????

问题代码:
  1. 假设我一直调用取消报名的接口,虽然对我没有影响,但是活动报名人数一直减。实际只减少一个报名人数,但是数据库却已经减了多次,报名的时候再进行人数判断的时候可能就会出问题了,引发报名人数超过限制的问题。。
  2. 更新活动人数的时候,有并发问题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(); }
调整逻辑
  1. 获取报名信息更新结果,更新成功后才执行更新活动人数。
  2. 更新活动人数,调整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 别乱用】,文章对比了distincdictinct ongroup by执行性能

问题SQL
  1. distinctgroup by使用
  2. 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"
调整逻辑
  1. group by 替换distinct
  2. 删除无用的distinct
  3. 把子查询都去掉

调整后的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
  1. 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 ;
调整逻辑
  1. 统一各表中的dept_id字段类型为bigint
  2. 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逻辑结构的清晰,关键字就别乱用了。

源文:最近发现同事的代码问题(第二篇)

如有侵权请联系站点删除!

技术合作服务热线,欢迎来电咨询!