正文
DROP
INDEX
idx_name
ON
`order`
;
通过上面的命令我们已经能够确认索引是有的,但它生效了没?此时你内心或许会冒出这样一个疑问。
那么,如何查看索引有没有生效呢?
答:可以使用
explain
命令,查看 mysql 的执行计划,它会显示索引的使用情况。
例如:
explain select * from `order` where code='002';
结果:
通过这几列可以判断索引使用情况,执行计划包含列的含义如下图所示:
说实话,sql语句没有走索引,排除没有建索引之外,最大的可能性是索引失效了。
下面说说索引失效的常见原因:
如果不是上面的这些原因,则需要再进一步排查一下其他原因。
此外,你有没有遇到过这样一种情况:明明是同一条 sql,只有入参不同而已。有的时候走的索引 a,有的时候却走的索引 b?
没错,有时候 mysql 会选错索引。
必要时可以使用
force index
来强制查询 sql 走某个索引。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
-
项目地址:https://github.com/YunaiV/yudao-cloud
-
视频教程:https://doc.iocoder.cn/video/
如果优化了索引之后,也没啥效果。
接下来试着优化一下 sql 语句,因为它的改造成本相对于 java 代码来说也要小得多。
下面给大家列举了 sql 优化的 15 个小技巧:
很多时候,我们需要在某个接口中,调用其他服务的接口。
比如有这样的业务场景:
在用户信息查询接口中需要返回:用户名称、性别、等级、头像、积分、成长值等信息。
而用户名称、性别、等级、头像在用户服务中,积分在积分服务中,成长值在成长值服务中。为了汇总这些数据统一返回,需要另外提供一个对外接口服务。
于是,用户信息查询接口需要调用用户查询接口、积分查询接口和成长值查询接口,然后汇总数据统一返回。
调用过程如下图所示:
调用远程接口总耗时 530ms = 200ms + 150ms + 180ms。
显然这种串行调用远程接口性能是非常不好的,调用远程接口总的耗时为所有的远程接口耗时之和。
那么如何优化远程接口性能呢?
上面说到,既然串行调用多个远程接口性能很差,为什么不改成并行呢?
如下图所示:
调用远程接口总耗时 200ms = 200ms(即耗时最长的那次远程接口调用)。
在 java8 之前可以通过实现
Callable
接口,获取线程返回结果。java8 以后通过
CompleteFuture
类实现该功能。
我们这里以 CompleteFuture 为例:
public UserInfo getUserInfo(Long id) throws InterruptedException, ExecutionException {
final UserInfo userInfo = new UserInfo();
CompletableFuture userFuture = CompletableFuture.supplyAsync(() -> {
getRemoteUserAndFill(id, userInfo);
return Boolean.TRUE;
}, executor);
CompletableFuture bonusFuture = CompletableFuture.supplyAsync(() -> {
getRemoteBonusAndFill(id, userInfo);
return Boolean.TRUE;
}, executor);
CompletableFuture growthFuture = CompletableFuture.supplyAsync(() -> {
getRemoteGrowthAndFill(id, userInfo);
return Boolean.TRUE;
}, executor);
CompletableFuture.allOf(userFuture, bonusFuture, growthFuture).join();
userFuture.get();
bonusFuture.get();
growthFuture.get();
return userInfo;
}
温馨提醒一下,这两种方式别忘了使用线程池。示例中我用到了 executor,表示自定义的线程池,为了防止高并发场景下,出现线程过多的问题。
上面说到的用户信息查询接口需要调用用户查询接口、积分查询接口和成长值查询接口,然后汇总数据统一返回。
那么,我们能不能把数据冗余一下,把用户信息、积分和成长值的数据统一存储到一个地方,比如:redis,存的数据结构就是用户信息查询接口所需要的内容。然后通过用户 id,直接从 redis 中查询数据出来,不就 OK了?
如果在高并发的场景下,为了提升接口性能,远程接口调用大概率会被去掉,而改成保存冗余数据的数据异构方案。
但需要注意的是,如果使用了数据异构方案,就可能会出现数据一致性问题。
用户信息、积分和成长值有更新的话,大部分情况下,会先更新到数据库,然后同步到 redis。但这种跨库的操作,可能会导致两边数据不一致的情况产生。
重复调用
在我们的日常工作代码中可以说随处可见,但如果没有控制好,会非常影响接口的性能。
不信,我们一起看看。
有时候,我们需要从指定的用户集合中,查询出有哪些是在数据库中已经存在的。
实现代码可以这样写:
public List queryUser(List searchList) {
if (CollectionUtils.isEmpty(searchList)) {
return Collections.emptyList();
}
List result = Lists.newArrayList();
searchList.forEach(user -> result.add(userMapper.getUserById(user.getId())));
return result;
}
这里如果有 50 个用户,则需要循环 50 次去查询数据库。我们都知道,每查询一次数据库,就是一次远程调用。
如果查询 50 次数据库,就有 50 次远程调用,这是非常耗时的操作。
那么,我们如何优化呢?
具体代码如下:
public List queryUser(List searchList) {
if (CollectionUtils.isEmpty(searchList)) {
return Collections.emptyList();
}
List ids = searchList.stream().map(User::getId).collect(Collectors.toList());
return userMapper.getUserByIds(ids);
}
提供一个根据用户 id 集合批量查询用户的接口,只远程调用一次,就能查询出所有的数据。
这里有个需要注意的地方是:id 集合的大小要做限制,最好一次不要请求太多的数据。要根据实际情况而定,建议控制每次请求的记录条数在 500 以内。
有些小伙伴看到这个标题,可能会感到有点意外,死循环也算?
代码中不是应该避免死循环吗?为啥还是会产生死循环?
有时候死循环是我们自己写的,例如下面这段代码:
while(true) {
if(condition) {
break;
}
System.out.println("do samething");
}
这里使用了 while(true) 的循环调用,这种写法在
CAS自旋锁
中使用比较多。
当满足 condition 等于 true 的时候,则自动退出该循环。
如果 condition 条件非常复杂,一旦出现判断不正确,或者少写了一些逻辑判断,就可能在某些场景下出现死循环的问题。
出现死循环,大概率是开发人员人为的 bug 导致的,不过这种情况很容易被测出来。
还有一种隐藏的比较深的死循环,是由于代码写得不太严谨导致的。如果用正常数据,可能测不出问题,但一旦出现异常数据,就会立即出现死循环。
如果想要打印某个分类的所有父分类,可以用类似这样的递归方法实现: