使用spring框架开发项目时,为了方便,喜欢使用@Transactional注解提供事务功能。
通过这几列可以判断索引使用情况,执行计划包含列的含义如下所示:说实话,sql语句没有走索引,排除没有建索引之外,最大的可能性是索引失效了。
远程调用、第三方服务
用DROPINDEX命令也行:
但如果锁加得不好,导致锁的粒度太粗,也会非常影响接口性能。
既然串行调用多个远程接口性能很差,为什么不改成并行呢?调用远程接口总耗时200ms=200ms
通过ALTERTABLE命令可以添加索引:
比如有个用户请求接口中,需要做业务操作,发站内通知,和记录操作日志。为了实现起来比较方便,通常我们会将这些逻辑放在接口中同步执行,势必会对接口性能造成一定的影响。片示例,发站内通知和用户操作日志功能,对实时性要求不高,即使晚点写库,用户无非是晚点收到站内通知,或者运营晚点看到用户操作日志,对业务影响不大,所以完全可以异步处理。
后来,随着业务的发展,表中数据量越来越多,就不得不加索引了。
锁粒度
查看整张表的建表语句,里面同样会显示索引情况。
使用线程池改造之后,接口逻辑如下:。
SQL优化
方法上加锁:
public synchronized doSave(String fileUrl) {
mkdir();
uploadFile(fileUrl);
sendMessage(fileUrl);
}
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;
}
也容易造成大事务,引发其他的问题。
项目刚开始的时候,由于表中的数据量小,加不加索引sql查询性能差别不大。
sql常见优化的15个方法
通常异步主要有两种:多线程和mq。
很多时候,我们需要在某个接口中,调用其他服务的接口。
为了防止多个线程并发修改某个共享数据,造成数据异常。
并行调用
下面说说索引失效的常见原因:如果不是上面的这些原因,则需要再进一步排查一下其他原因。
优化大事务
温馨提醒一下,这两种方式别忘了使用线程池。示例中用到了executor,表示自定义的线程池,为了防止高并发场景下,出现线程过多的问题。
java中提供了synchronized关键字给代码加锁。
在用户信息查询接口中需要返回:用户名称、性别、等级、头像、积分、成长值等信息。
比如有这样的业务场景:
我们都知道文件上传操作是非常耗时的,如果将整个方法加锁,那么需要等到整个方法执行完之后才能释放锁。显然,这会导致该方法的性能很差,变得得不偿失。
将查询(select)方法放到事务外事务中避免远程调用事务中避免一次性处理太多数据有些功能可以非事务执行有些功能可以异步处理
我们能不能把数据冗余一下,把用户信息、积分和成长值的数据统一存储到一个地方,比如:redis,存的数据结构就是用户信息查询接口所需要的内容。然后通过用户id,直接从redis中查询数据出来,不就OK了?
能单独查看某张表的索引情况。
用户信息、积分和成长值有更新的话,大部分情况下,会先更新到数据库,然后同步到redis。但这种跨库的操作,可能会导致两边数据不一致的情况产生。
没加索引
为了解决并发场景下,多个线程同时修改数据,造成数据不一致的情况。通常情况下,我们会:加锁。
索引没生效
可以通过命令:
show index from `表名`;
数据异构
如果在高并发的场景下,为了提升接口性能,远程接口调用大概率会被去掉,而改成保存冗余数据的数据异构方案。但需要注意的是,如果使用了数据异构方案,就可能会出现数据一致性问题。
必要时可以使用forceindex来强制查询sql走某个索引。
也可以通过命令:
show create table `表名`;
下面用一张看看大事务引发的问题。
删除索引可以用DROPINDEX命令:
sql语句中where条件的关键字段,或者orderby后面的排序字段,忘了加索引,这个问题在项目中很常见。
也可以通过CREATEINDEX命令添加索引:
于是,用户信息查询接口需要调用用户查询接口、积分查询接口和成长值查询接口,然后汇总数据统一返回。
redis分布式锁
调用过程如下所示:
没错,有时候mysql会选错索引。
上面说到的用户信息查询接口需要调用用户查询接口、积分查询接口和成长值查询接口,然后汇总数据统一返回。
串行调用远程接口性能是非常不好的,调用远程接口总的耗时为所有的远程接口耗时之和。
而用户名称、性别、等级、头像在用户服务中,积分在积分服务中,成长值在成长值服务中。为了汇总这些数据统一返回,需要另外提供一个对外接口服务。
以使用explain命令,查看mysql的执行计划,它会显示索引的使用情况:
选错索引
此外,你有没有遇到过这样一种情况:明明是同一条sql,只有入参不同而已。有的时候走的索引a,有的时候却走的索引b?
通常有两种写法:在方法上加锁和在代码块上加锁。
异步处理
调用远程接口总耗时530ms=200ms+150ms+180ms
目前在mysql中如果想要修改索引,只能先删除索引,再重新添加新的。使用navicatsql工具可以可视化修改
索引失效
避免大事务
在java8之前可以通过实现Callable接口,获取线程返回结果。
使用@Transactional注解这种声明式事务的方式提供事务功能,能少写很多代码,提升开发效率。
提示:以下是本篇文章正文内容,下面案例可供参考
文章为作者独立观点,不代表股票交易接口观点