1、索引
1.1、忘加索引
通过对线上日志的查询,发现一些SQL的执行时间过长,SQL中where和order by的语句中的字段忘记添加索引
查看索引和表结构:
SHOW INDEX FROM 表名;
SHOW CREATE TABLE 表名
添加索引:
ALTER TABLE 表名 ADD INDEX 索引名(列名);
CREATE INDEX 索引名 ON 表名(列名)
无法修改索引,一旦修改就要先删除后添加
DROP INDEX 索引名 ON 表名 ;
1.2、索引失效
EXPLAIN SELECT *FROM 表名 where 列名='具体值'
这三列的值可以看见是否使用了索引
1.3、选错索引
可以用FORCE INDEX强制使用具体的某个索引
SELECT *FROM 表名 FORCE INDEX(索引名) WHERE ...
2、SQL优化
3、远程调用
3.1、并行调用
改串行调用为并行调用
completeFuture类
3.2、数据异构
保存数据冗余的数据异构解决方案,但可能会出现数据一致性问题
4、重复调用
4.1、循环查数据库
当需要批量查询数据库时,不应该改变查询条件多次查询
可以提供一个接口,一次查询达到批量查询的效果
4.2、死循环
while(true) {
if(condition)
break;
}
System. out. println("do samething");
使用这种写法,当condition满足条件时,能跳出此循环,当一旦不满足该条件则会陷入死循环
4.3、无限递归
递归时,如果不设置的递归深度,在使用递归时的结束条件达不到,也会陷入无限递归中,发生堆栈溢出的结果,所以尽量明确递归的深度,即使结束条件达不到,深度达到也要结束递归
5、异步处理
核心逻辑可以同步执行,同步写库;
非核心逻辑可以异步执行,异步写库;
异步处理通常可以使用线程池和mq
5.1、线程池
将核心逻辑和非核心逻辑提交到不同线程池中执行
但这样也会有缺陷,如果服务器重启或是执行的功能出现异常,无法重试,会出现数据丢失
5.2、MQ
发送mq消息给mq服务器,这样我们只需关注业务执行操作的代码即可
对应服务的去mq服务器消费消息
6、使用大事务
减少使用@Transactional注解
将select查询放到事务外
事务中避免远程调用
事务中避免处理太多数据
有些功能可以非事务执行
有些功能可以异步处理
7、锁粒度
为了防止多个线程并发修改某个共享数据,造成数据不一致的异常情况,我们会使用锁
7.1、synchronized
synchronized关键字给代码加锁,在方法上加锁和在代码块上加锁
加锁时需要将粒度缩小到需要加锁的地方,用代码块加锁替代在方法上加锁
单体式应用可以使用此方法加锁,为了保证服务的稳定性,同一个服务会被部署在多个节点上,即使挂了一个节点,其他节点的服务任然可用,多节点部署避免了因为某个节点挂了导致服务不可用的情况,同时也能分摊整个系统的流量,避免系统压力过大,这时synchronized就不适用了,而需要使用分布式锁:Redis分式锁、Zookeeper分布式锁和数据库分布式锁(表锁、行锁、间隙锁)
8、分页处理
当需要进行大批量数据的查询业务时,可以将一次查询分为多次查询,最后返回时再进行汇总
8.1、同步调用
当远程调用接口对请求耗时有要求,可以使用同步分页调用查询接口
8.2、异步调用
当对本身接口和远程调用接口均有耗时要求,可以使用异步分页请求的方式解决,使用Completablefuture类解决该问题,多个线程异步调用远程接口,最后汇总结果统一返回。
9、加缓存
当并发业务量较大,或者一些频繁请求接口的常用数据,可以使用缓存的方法解决该问题,但同时会带来数据不一致的问题
9.1、Redis缓存
当需要请求一个常访问数据时,可以先在缓存中读取,缓存中不存在再去数据库中请求,但同时需要有一个定时任务,去查询数据库更新Redis保持数据的一致性
9.2、二级缓存
即使使用Redis也是远程请求,可以使用本地的内存作为缓存,省去了远程请求的时间,例如guava、Ehcache、caffeine等缓存框架
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-cache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>2.7.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.1</version>
</dependency>
cacheManager
@Configuration
@EnableCaching
public class Cacheconfig{
@Bean
public CacheManager cacheManager(){
CaffeinecacheManager caffeinecacheManager =new CaffeinecacheManager();
//设置需要的属性
return caffeinecacheManager;
}
}
使用@Cacheable注解获取缓存值
@Cacheable(value='',key='')
缺点有:数据更新了,不能及时刷新缓存;如果有多台服务器节点,可能会有多个节点数据不一致的情况。因此存在数据不一致问题,该方法尽量使用在一些数据不敏感数据上。
10、分库分表
接口性能受限,是并发量过大,请求数据库压力过大导致接口性能受限。
通过路由算法计算得到该条数据应存入哪个库哪张表,
分库是为了解决数据库连接资源不足的问题和磁盘IO的性能瓶颈问题
分表是为了解决单表数据量太大,SQL语句查询数据时,即使走索引也非常耗时的问题
访问量大,数据量小可以只分库不分表,相反可以只分表不分库
11、辅助功能
11.1、开启慢查询日志
确定SQL的性能瓶颈,需要开启MySQL的慢查询日志,把超过指定时间的sql语句单独记录下来,方便后面分析和解决问题
参数:
<!--慢查询开关-->
set global_query_log ='on';
<!--慢查询日志存放的路径-->
set global_query_log_file ='/user/local/mysql/data/slow.log';
<!--超过多少秒才会记录日志-->
set long_query_time =2;
11.2、开启监控
出现SQL问题时及时发现处理,需要对系统做监控
Prometheus可以用来监控
①接口响应时间;
②调用第三方服务耗时;
③慢查询SQL耗时
④cpu使用情况
⑤内存使用情况
⑥磁盘使用情况
⑦数据库使用情况
11.3、链路追踪
当接口中处理很多例如:查询数据库、mq消息、Redis请求、远程服务调用
可以使用skywalking定位性能问题