提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
提示:这里可以添加本文要记录的大概内容:
例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。
提示:以下是本篇文章正文内容,下面案例可供参考
1、索引失效
1.1没加索引
sql语句中where条件的关键字段,或者order by后面的排序字段,忘了加索引,这个问题在项目中很常见。
项目刚开始的时候,由于表中的数据量小,加不加索引sql查询性能差别不大。
后来,随着业务的发展,表中数据量越来越多,就不得不加索引了。
可以通过命令:
showindexfrom`表名`;
能单独查看某张表的索引情况。
也可以通过命令:
showcreatetable`表名`;
查看整张表的建表语句,里面同样会显示索引情况。
通过
ALTER TABLE
命令可以添加索引:
ALTERTABLE`order`ADDINDEX idx_name (name);
也可以通过CREATE INDEX命令添加索引:
CREATEINDEX idx_name ON`order`(name);
目前在mysql中如果想要修改索引,只能先删除索引,再重新添加新的。
使用
navicat sql
工具可以可视化修改
删除索引可以用
DROP INDEX
命令:
ALTERTABLE`order`DROPINDEX idx_name;
用DROP INDEX命令也行:
DROPINDEX idx_name ON`order`;
1.2 索引没生效
以使用
explain
命令,查看mysql的执行计划,它会显示索引的使用情况:
explainselect*from`order`where code='002';
通过这几列可以判断索引使用情况,执行计划包含列的含义如下图所示:
说实话,sql语句没有走索引,排除没有建索引之外,最大的可能性是索引失效了。
下面说说索引失效的常见原因:
如果不是上面的这些原因,则需要再进一步排查一下其他原因。
1.3 选错索引
此外,你有没有遇到过这样一种情况:明明是同一条sql,只有入参不同而已。有的时候走的索引a,有的时候却走的索引b?
没错,有时候mysql会选错索引。
必要时可以使用
force index
来强制查询sql走某个索引。
2、 SQL优化
sql常见优化的15个方法
3、远程调用、第三方服务
很多时候,我们需要在某个接口中,调用其他服务的接口。
比如有这样的业务场景:
在用户信息查询接口中需要返回:用户名称、性别、等级、头像、积分、成长值等信息。
而用户名称、性别、等级、头像在用户服务中,积分在积分服务中,成长值在成长值服务中。为了汇总这些数据统一返回,需要另外提供一个对外接口服务。
于是,用户信息查询接口需要调用用户查询接口、积分查询接口 和 成长值查询接口,然后汇总数据统一返回。
调用过程如下图所示:
调用远程接口总耗时 530ms = 200ms + 150ms + 180ms
串行调用远程接口性能是非常不好的,调用远程接口总的耗时为所有的远程接口耗时之和。
3.1 并行调用
既然串行调用多个远程接口性能很差,为什么不改成并行呢?
调用远程接口总耗时 200ms = 200ms(即耗时最长的那次远程接口调用)
在java8之前可以通过实现Callable接口,获取线程返回结果。
java8以后通过CompleteFuture类实现该功能。我们这里以CompleteFuture为例:
publicUserInfogetUserInfo(Long id)throwsInterruptedException,ExecutionException{finalUserInfo userInfo =newUserInfo();CompletableFuture userFuture =CompletableFuture.supplyAsync(()->{getRemoteUserAndFill(id, userInfo);returnBoolean.TRUE;}, executor);CompletableFuture bonusFuture =CompletableFuture.supplyAsync(()->{getRemoteBonusAndFill(id, userInfo);returnBoolean.TRUE;}, executor);CompletableFuture growthFuture =CompletableFuture.supplyAsync(()->{getRemoteGrowthAndFill(id, userInfo);returnBoolean.TRUE;}, executor);CompletableFuture.allOf(userFuture, bonusFuture, growthFuture).join();
userFuture.get();
bonusFuture.get();
growthFuture.get();return userInfo;}
温馨提醒一下,这两种方式别忘了使用线程池。示例中用到了executor,表示自定义的线程池,为了防止高并发场景下,出现线程过多的问题。
3.2 数据异构
上面说到的用户信息查询接口需要调用用户查询接口、积分查询接口 和 成长值查询接口,然后汇总数据统一返回。
那么,我们能不能把数据冗余一下,把用户信息、积分和成长值的数据统一存储到一个地方,比如:redis,存的数据结构就是用户信息查询接口所需要的内容。然后通过用户id,直接从redis中查询数据出来,不就OK了?
如果在高并发的场景下,为了提升接口性能,远程接口调用大概率会被去掉,而改成保存冗余数据的数据异构方案。
但需要注意的是,如果使用了数据异构方案,就可能会出现数据一致性问题。
用户信息、积分和成长值有更新的话,大部分情况下,会先更新到数据库,然后同步到redis。但这种跨库的操作,可能会导致两边数据不一致的情况产生。
4、异步处理
比如有个用户请求接口中,需要做业务操作,发站内通知,和记录操作日志。为了实现起来比较方便,通常我们会将这些逻辑放在接口中同步执行,势必会对接口性能造成一定的影响。
图片示例,发站内通知和用户操作日志功能,对实时性要求不高,即使晚点写库,用户无非是晚点收到站内通知,或者运营晚点看到用户操作日志,对业务影响不大,所以完全可以异步处理。
通常异步主要有两种:
多线程
和
mq
。
使用
线程池
改造之后,接口逻辑如下:。
5、避免大事务
使用
spring
框架开发项目时,为了方便,喜欢使用@Transactional注解提供事务功能。
使用
@Transactional
注解这种声明式事务的方式提供事务功能,能少写很多代码,提升开发效率。
也容易造成大事务,引发其他的问题。
下面用一张图看看大事务引发的问题。
优化
大事务
- 将查询(select)方法放到事务外
- 事务中避免远程调用
- 事务中避免一次性处理太多数据
- 有些功能可以非事务执行
- 有些功能可以异步处理
6、锁粒度
为了防止多个线程并发修改某个共享数据,造成数据异常。
为了解决并发场景下,多个线程同时修改数据,造成数据不一致的情况。通常情况下,我们会:加锁。
但如果锁加得不好,导致锁的粒度太粗,也会非常影响接口性能。
6.1 synchronized
java
中提供了synchronized关键字给代码加锁。
通常有两种写法:在方法上加锁 和 在代码块上加锁。
方法
上加锁:
publicsynchronizeddoSave(String fileUrl){mkdir();uploadFile(fileUrl);sendMessage(fileUrl);}
加锁的目的是为了防止并发的情况下,创建了相同的目录,第二次会创建失败,影响业务功能。
但这种直接在方法上加锁,锁的粒度有点粗。因为doSave方法中的上传文件和发消息方法,是不需要加锁的。只有创建目录方法,才需要加锁。
我们都知道文件上传操作是非常耗时的,如果将整个方法加锁,那么需要等到整个方法执行完之后才能释放锁。显然,这会导致该方法的性能很差,变得得不偿失。
这时,我们可以改成在代码块上加锁了,具体代码如下:
publicvoiddoSave(String path,String fileUrl){synchronized(this){if(!exists(path)){mkdir(path);}}uploadFile(fileUrl);sendMessage(fileUrl);}
6.2 redis分布式锁
版权归原作者 祝怂怂 所有, 如有侵权,请联系我们删除。