Troubleshooting系列-启动ShardingJDBC后首次查询慢问题分析及解决

前言:之前使用shardingjdbc5.2版本作为客户端分库分表组件后,经开发同学反馈有个比较奇怪的问题。就是启动后首次SQL执行速度会比较慢,后面就正常。本文主要基于此问题进行重现和解决。

问题重现

本文源代码基于之前文章MYSQL系列-分库分表(三):Sharding-JDBC实现分库分表落地实践-上

新增CouponController用于调试

java 复制代码
@RestController
@RequestMapping("/coupon")
public class CouponController {
    private static final Logger LOGGER = LoggerFactory.getLogger(CouponController.class);

    @Resource
    private CouponInfoMapper couponInfoMapper;

    @PostMapping(path = "/add")
    public void addCouponCode(CouponInfo couponInfo) {
        couponInfoMapper.addCouponCode(couponInfo);
    }

    @GetMapping(path = "/query")
    public List<CouponInfo> selectByCouponCode(String couponCode, String country) {
        long start = System.currentTimeMillis();
        List<CouponInfo> couponInfoList = couponInfoMapper.selectByCouponCode(couponCode, country);
        LOGGER.info("selectByCouponCode time={}", System.currentTimeMillis() - start);
        return couponInfoList;
    }
}

在浏览器执行两次调用请求后

bash 复制代码
http://localhost:8088/h/coupon/query?couponCode=1234&country=CN

日志如下,第一次964ms,第二次6ms

分析定位

定位这种耗时问题可以采用阿里开源的Java诊断工具 Arthas(阿尔萨斯)

安装启动非常便捷

shell 复制代码
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

启动后执行命令,在期间执行两次命令

ruby 复制代码
[arthas@2111]$ profiler start
Profiling started
[arthas@2111]$ profiler stop --format html
OK
profiler output file: /home/toby/dynamic/bin/arthas-output/20231022-204409.html
[arthas@2111]$

获得火焰图结果

分析如下方法在执行较慢

bash 复制代码
org/apache/shardingsphere/driver/jdbc/core/connection/ShardingSphereConnection.prepareStatement
org/apache/calcite/rel/rules/CoreRules.<clinit>

org/apache/shardingsphere/infra/parser/ShardingSphereSQLParserEngine.parse
org/apache/shardingsphere/sql/parser/core/SQLParserFactory.newInstance
org/apache/shardingsphere/sql/parser/core/SQLParserFactory.createSQLParser和java/lang/reflect/Constructor.newInstance

分析CoreRules,发现其主要是静态类的加载,应该只会执行一次

java 复制代码
public class CoreRules {

  private CoreRules() {}

  /** Rule that recognizes an {@link Aggregate}
   * on top of a {@link Project} and if possible
   * aggregates through the Project or removes the Project. */
  public static final AggregateProjectMergeRule AGGREGATE_PROJECT_MERGE =
      AggregateProjectMergeRule.Config.DEFAULT.toRule();

  /** Rule that removes constant keys from an {@link Aggregate}. */
  public static final AggregateProjectPullUpConstantsRule
      AGGREGATE_PROJECT_PULL_UP_CONSTANTS =
      AggregateProjectPullUpConstantsRule.Config.DEFAULT.toRule();

分析SQLParserFactory.newInstance,最终会执行到反射相关代码,应该也是执行一次就好了

java 复制代码
public static SQLParser newInstance(final String sql, final Class<? extends SQLLexer> lexerClass, final Class<? extends SQLParser> parserClass) {
    return createSQLParser(createTokenStream(sql, lexerClass), parserClass);
}
java 复制代码
private static ReflectionFactory getReflectionFactory() {
    if (reflectionFactory == null) {
        reflectionFactory =
            java.security.AccessController.doPrivileged
                (new sun.reflect.ReflectionFactory.GetReflectionFactoryAction());
    }
    return reflectionFactory;
}

分析到这里后,发现第一次执行SQL,会经历很多的预加载,分析基本上都是一次性的,sharding还有执行sql相关的缓存,不过从分析结果来看,不是影响执行耗时的主要原因。

解决措施

可以在系统启动后,自动执行一个查询sql自动预加载上述比较耗时的操作,这样真正的业务SQL进来后,就不会很慢了。具体的代码如下:

java 复制代码
@Component
public class ShardingPreLoadService implements InitializingBean {
    private static final Logger LOGGER = LoggerFactory.getLogger(ShardingPreLoadService.class);
    @Resource
    private DataSource dataSource;

    @Override
    public void afterPropertiesSet() throws Exception {
        Connection connection = dataSource.getConnection();
        String sql = "select 1";
        connection.prepareStatement(sql).execute();
        LOGGER.info("ShardingPreLoadService load end.");
    }
}

优化后,第一次调用167ms,比之前900多优化很多。第二次执行6ms因为有sharding执行计划缓存、MYSQL本身buffer优化,这块是不好优化到6ms这么少的。

相关推荐
瓯雅爱分享24 分钟前
Java+Vue构建的采购招投标一体化管理系统,集成招标计划、投标审核、在线竞价、中标公示及合同跟踪功能,附完整源码,助力企业实现采购全流程自动化与规范化
java·mysql·vue·软件工程·源代码管理
追逐时光者3 小时前
推荐 12 款开源美观、简单易用的 WPF UI 控件库,让 WPF 应用界面焕然一新!
后端·.net
Jagger_3 小时前
敏捷开发流程-精简版
前端·后端
mit6.8243 小时前
[C# starter-kit] 命令/查询职责分离CQRS | MediatR |
java·数据库·c#
诸神缄默不语3 小时前
Maven用户设置文件(settings.xml)配置指南
xml·java·maven
GISer_Jing3 小时前
ByteDance——jy真题
前端·javascript·面试
任子菲阳3 小时前
学Java第三十四天-----抽象类和抽象方法
java·开发语言
苏打水com3 小时前
数据库进阶实战:从性能优化到分布式架构的核心突破
数据库·后端
莫叫石榴姐4 小时前
SQL百题斩:从入门到精通,一站式解锁数据世界
大数据·数据仓库·sql·面试·职场和发展
学Linux的语莫4 小时前
机器学习数据处理
java·算法·机器学习