解决方案|数据库查询时间过长

解决方案|数据库查询时间过长


前言

一次线上故障,数据库查询时间过长,导致前端页面频频报错,结果不仅该服务的访问受到了影响,其他服务的访问的流畅度也下降了。

分析

  1. 查询语句并不复杂,只涉及单表查询
  2. 查询已经设置了分页,也有加索引
  3. 查看该表的数据量,已经有两千万

解决

阶段一

看到数据量已经有两千万,是不是有人觉得我立刻就会讲分表、分区等操作。哈哈,当然不是了,线上问题当然应该尽快解决。

为了保证其他服务正常执行,并结合该服务的特点(访问量不会太多),直接设置该服务的最大查询时间,查询时间超过限制,则把错误日志打印出来,然后返回,保证其他服务的可用性。

设置的最大查询时间应该只局限于该服务,即粒度要小,别影响到其他服务。所以采用如下方式:

 Future<List<Repor>> result = asyncService.get(reportDetailedsExample);
 List<ReportDetailed> reportDetailed = null;
 try {
    
    
     reportDetailed = result.get(5, TimeUnit.SECONDS);
 } catch (Exception e) {
    
    
      throw new BusinessException("慢查询异常");
 }
 if (reportDetailed == null || reportDetailed.isEmpty()) {
    
    
      return null;
 }
 // ...

采用Future的形式,异步获取结果,get方法设置等待时间为5秒。异步服务类AsyncService ,采用AsyncResult包裹查询结果。

/***
 *
 * @Author:fsn
 * @Date: 2020/4/16 17:14
 * @Description
 */

@Service
public class AsyncService {
    
    
    @Autowired
    private ReportDetailedsMapper reportDetailedsMapper;

    @Async
    public Future<List<ReportDetaileds>> get(ReportDetailedsExample reportDetailedsExample) {
    
    
        return new AsyncResult<>(reportDetailedsMapper.selectByExampleWithBLOBs(
                reportDetailedsExample));
    }
}

阶段二

分表or分区,最终方案是分区。先进行一波操作,再说说缘由~

这里采用比较常见的RANGE分区,注意!!!分区键(这里是date)必须是主键的一部分!

ALTER TABLE report_detaileds_copy1 PARTITION BY RANGE (YEAR(`date`))
(   
	 PARTITION p2019 VALUES less than (2019),
	 PARTITION p2020 VALUES less than (2020)
	);

上述这种方式需要服务访问量比较低的情况下才做比较好,一般来说,可以再新建一张表,然后分区,再导数据到新表(导数据的时候千万小心!!!特别是表的更新、插入都很频繁的时候,还得注意是否有走索引(不是说加了索引就一定会走索引),避免锁整张表的情况发生)。

CREATE TABLE `report_detaileds_2020` (
  // 此次省略一大波字段
  PRIMARY KEY (`id`, `date`) USING BTREE,
  KEY `rule_id` (`rule_id`) USING BTREE
) PARTITION BY RANGE (YEAR(`date`))(
    PARTITION p2019 VALUES less than (2019),
    PARTITION p2020 VALUES less than (2020)
);

为什么我这里采用分区呢?(1)由于业务特点,显示的信息是根据时间显示的;(2)这些信息不会全部对用户公开,只显示了一段时间内的数据;(3)对于一张大数据量的表进行分表工作量还是挺大的,还得涉及代码层面的修改,而采用merge的分表形式虽然比较简单但受限于存储引擎(需要MyISAM存储引擎,如果能在代码设计的时候,可以预估到数据量未来的大概增长情况,还是早做分表稍微好点)

注意!!
分区方案也不是随便划分的,它的缺点如下:(1)对分区表进行DDL操作难度更大风险高。(DDL操作需要锁定所有分区,导致所有分区上操作都被阻塞)(2)分区不当,导致扫描全部分区,可能导致IO次数反而更多了。

关于第二点的解释:

以Innodb存储引擎文件,我们的一个表的数据和索引保存的地方是在一个idb为后缀名的文件里头,对于分区表来说,原来的一个idb文件现在是有多个的,对应多个分区。而我们知道,InnoDb采用B+树作为索引结构,一般2次IO左右的次数就可以扫描所有数据了。而如果扫描所有分区的话,一个分区2次IO,10个分区那就是20次IO。。。。

拓展

其实还有一个更加骚的办法,文中说过该业务的特点之一是这些信息不会全部对用户公开,只显示了一段时间内的数据,我们可以写个脚本,每天晚上或者凌晨,把这段时间的数据捞出来,存到一个表中(存之前truncate一下),然后只针对该表进行查询。

更多分区内容可以看看官方文档

猜你喜欢

转载自blog.csdn.net/legendaryhaha/article/details/106182416