目录
总体分析
前言:
关于sql优化
关于代码优化
实例
部分细节优化
工作总结
问题总结
数据库事务问题(一)
数据库事务问题(二)
每次工作总结
第一次
第二次
第三次
第四次
第五次
第六次
一般可以使用Arthas大概分析具体哪个步骤耗时过长,根据耗时过长的代码进行分析,一般都是sql执行时间过长
如果数据库执行时间不长的话
需要看一下返回的数据类型和代码中定义的字段类型是否相同
其次可以看一下是否因为返回太多字段信息,有些sql会把所有字段信息进行返回,并且这些字段也要进行序列化和反序列化进行映射也要耗时
如果数据库执行时间长的话
就看一下sql执行计划,是否用到索引,没有的话就需要加上索引,这样修改代价降到最低
加索引的话,尽可能加上复合索引
如果加上索引之后,索引失效就需要看看sql是否有必要进行改写了,尽可能使用上索引
如果加上索引之后,索引生效,但是耗时还是很长的话,就跟本身sql没啥关系,应该也不会出现这种情况(就是走索引速度还不快)
拓展一下复合索引的好处:
建了一个(a,b,c)的复合索引,那么实际等于建了(a),(a,b),(a,b,c)三个索引,因为每多一个索引,都会增加写操作的开销和磁盘空间的开销。对于大量数据的表,这可是不小的开销!
覆盖索引。同样的有复合索引(a,b,c),如果有如下的sql: select a,b,c from table where a=1 and b = 1。那么MySQL可以直接通过遍历索引取得数据,而无需回表,这减少了很多的随机io操作。减少io操作,特别的随机io其实是dba主要的优化策略。所以,在真正的实际应用中,覆盖索引是主要的提升性能的优化手段之一。
循环中有数据库查询,不断与数据库进行交互耗时是最严重的
如果想不修改循环中的代码逻辑,就只能需要进行异步批量查询数据库,就是多线程的方式
直接修改代码逻辑,尽量避免循环里面对数据库进行查询
数据查询的慢,根本原因还是一次性查询的数据太多了,可以做分页查询,或者分批调用接口
从20s优化到500ms,我用了这三招_苏三说技术的博客-CSDN博客
上面这个文章主要就是加索引,多线程,分页查询这三步
1. 如果对大量数据进行操作的时候,提升优化就是分批操作,例如我们这边es数据查询就是每批50个
2. 比方说根据大量服务id请求对应的数据,因为数据量太大,对请求也进行分批操作,就是分批id包装为请求体发送
3. 有些耗时,但是不影响用户的功能就可以走异步实现
4. 他们认为加上本地缓存会更快
备注:一个一个数据进行操作,跟批量数据操作的话,应该是批量操作的速度要快一些吧,主要是节约了频繁与数据库或者与redis进行连接这个时间(就算你用连接池的话,也是需要与数据库或者redis服务端建立连接的过程)
问题场景:排序修改,数据库中一个数据的排序发生改变,其他的顺序都跟着变动,比方说原来序号为2的数据,现在改为10,那么3-9的数据序号都要进行-1操作
实际发生情况:偶现,多个数据序号重复,序号之间会有间隔(原来的需求就是为了序号之间没有间隔)
发生原因:我感觉是不可重复读,可能同时有两个事务都在修改数据,只不过一个事务读到了另一个事务提交后的数据,它本来要读的是另一个数据提交前的数据,就是每次读到的数据不一样,这不就是不可重复读嘛,就那个数据读的是另一个事务提交后的数据,其他数据读的都是正常的数据,就会导致重复,也可能存在幻读,因为有可能读到提交后的数据
解决方案:等待中,我感觉更改排序的时候加上读写锁吧,这样改的话并发量大的话,会引起阻塞,我看他们有一个方案是,拿更改之后的全部数据,写到库里面,这个操作就太扯了,正常的话应该加上行锁,尽可能减小锁的粒度
拓展:就像这种批量修改数据,并行执行事务,对数据准确性要求比较严格的话,就会有问题,正常情况下,数据都是更新为其他不同的值,与你拿到的是更新前或者更新后的值无关,只看最后更新成啥,但是你更新的值与你之前获得的值相关的话,就会出现并发问题
问题场景:事务还没有执行完,执行业务逻辑,读取到的是老数据
举例说明:
如果不加锁的话,事务还没有执行完,别的线程进来拿数据,拿的肯定是老数据,这样是不行的,所以需要将事务单独将一个方法写出来,一定要确保事务执行完成
举一个加锁的例子:
这锁在和@Transational同一个方法一起使用的情况下会出现,锁已经去除,但是事务还没提交的情况,造成脏读和数据不一致性等情况.假设你事务里面执行时间长了,**由于事务的提交在方法运行结束之后,并且事务真正启动在执行到它们之后的第一个操作 InnoDB 表的语句的时候**,看下面这张图,就是线程a执行test这个方法的时候,线程a已经解锁了,但是事务可能刚刚提交数据还没有变更,线程b这个时候进来执行的话,读取的还是原来的数据,这样就是有问题的
@Transactional(rollbackFor = Exeception.class)
public void test() {
lock.lock();
try {
// 业务逻辑
} finally {
lock.unlock();
}
}
解决:可以把@Transational和业务逻辑的代码单独提到一个service里
@Transactional
public void update(int id) {
/*
业务代码
*/
}
这样的话呢,就是update这个方法是个事务,这个事务执行完成后,才会去解锁,数据会保持一致性。重点是@transactional这个方法执行完才会提交事务,而上面那种的锁已经释放,还没有提交事务的话,就会引起数据不一致问题
还有一点就是看看上面是业务代码都在一个方法里面,所以@Transactional这个注解一般放在service层,之前做业务的时候,还有这样的问题,功能是这样的,先将原来数据status置0,然后插入数据,另一个表的数据也有置0操作,这样的业务逻辑都放在@Transactional方法里面的话,不加锁,线程a假设正在提交事务的话,此时线程b进来插入数据,线程a有可能把线程b插进来的数据失效了,按道理来说应该加把锁