Sharding-JDBC 源码之 SQL 解析

Sharding-JDBC 系列

  1. 第一篇 Sharding-JDBC 源码之启动流程分析
  2. 第二篇 Sharding-JDBC 源码之 SQL 解析(本文)
  3. 第三篇 Sharding-JDBC 源码之 SQL 路由
  4. 第四篇 Sharding-JDBC 源码之 SQL 改写
  5. 第五篇 Sharding-JDBC 源码之 SQL 执行
  6. 第六篇 Sharding-JDBC 源码之结果集归并

在执行 SQL 时,Sharding-JDBC 需要根据启动时获取到的数据源、路由等配置文件获取真正执行的 SQL,那在获取真正执行的语句之前还需要对 SQL 进行解析、路由,今天我们来看下 Sharding-JDBC 是如何解析 SQL 语句的。

本文以查询语句为例进行分析,数据源及分库分表配置和之前保持一致。
从源码看,SQL 解析工作主要在 Sharding-JDBC 源码之 SQL 解析 的 parsing 包中:
在这里插入图片描述
解析过程分为词法解析语法解析。 词法解析器用于将SQL拆解为不可再分的原子符号,称为Token。并根据不同数据库方言所提供的字典,将其归类为关键字表达式字面量操作符。 再使用语法解析器将SQL转换为抽象语法树

  • cache 包:缓存解析过 SQL 语句;
  • lexer 包:词法解析,封装不同方言(MysqlOracle 等)的解析器,使用 LexerEngine 完成词法分析;
  • parser 包:语法解析,根据 SQL 语句的类型完成解析工作。

在看具体执行流程前,先了解下 parsing 封装的集中 SQL 语句类型:

  • dal:数据访问语言(Data Access Language) 例: use、desc、describe、show
  • ddl:数据定义语言(Data Definition Language) 例:create、alter、drop、truncate
  • dml:数据操作语言(Data Manipulation Language) 例: insert、update、delete
  • dql:数据查询语言(Data Query Language) 例: select
  • tcl:事务控制语言(Transaction Control Language) 例: set、commit、rollback、savepoint、begin。

在执行语句时,需要进行路由,而进行路由,需要对 SQL 进行解析,下面看下解析的具体执行流程:

获取 SQL 解析引擎

由于我们使用的是标准分片策略,所以获取到的解析引擎是 SQLParsingEngine,而该引擎中的关键方法是 parse

public final class ParsingResultCache {
    // 省略部分代码
    // 逻辑 SQL 缓存    
    private volatile Map<String, SQLStatement> cache = new WeakHashMap<>(65535, 1);
    // 省略部分代码
}

public final class SQLParsingEngine {
    ......
    
    public SQLStatement parse(final boolean useCache) {
        // 从缓存中获取解析结果
        Optional<SQLStatement> cachedSQLStatement = getSQLStatementFromCache(useCache);
        // 缓存中存在则直接获取返回解析结果
        if (cachedSQLStatement.isPresent()) {
            return cachedSQLStatement.get();
        }
        
        // 根据数据库类型获取具体方言词法分析器
        LexerEngine lexerEngine = LexerEngineFactory.newInstance(dbType, sql);
        lexerEngine.nextToken();
        // 根据逻辑 SQL 类型获取具体语句解析器,并解析语言
        SQLStatement result = SQLParserFactory.newInstance(dbType, lexerEngine.getCurrentToken().getType(), shardingRule, lexerEngine, shardingMetaData).parse();
        // 将解析结果缓存到 cache 对象中
        if (useCache) {
            ParsingResultCache.getInstance().put(sql, result);
        }
        return result;
    }
    
    // 从缓存中获取解析结果
    private Optional<SQLStatement> getSQLStatementFromCache(final boolean useCache) {
        return useCache ? Optional.fromNullable(ParsingResultCache.getInstance().getSQLStatement(sql)) : Optional.<SQLStatement>absent();
    }
}    
  1. ParsingResultCache 类中的 cache 对象主要用来缓存解析过的逻辑 SQL,避免重复解析。同时 cache 使用的是 WeakHashMap,这样可以保证不用手动释放引用且保证内存不被过度使用,因为弱引用对象只能存活到下一次 GC 之前,
    缓存的 key 形如:select goods_name from t_goods where goods_id=?
  2. 本例中,是 select 查询语句,所以获取的是 MySQLSelectParser,执行该解析器的 parseInternal 方法,其他类型的语句根据解析器的不同,在执行 parse 方法时进入具体的实现类中。

解析 SQL

解析逻辑 SQL,将解析结果封装为 SelectStatement 对象

    public MySQLSelectParser(final ShardingRule shardingRule, final LexerEngine lexerEngine, final ShardingMetaData shardingMetaData) {
        // 1. 创建 DQL 门面解析器
        super(shardingRule, lexerEngine, new MySQLSelectClauseParserFacade(shardingRule, lexerEngine), shardingMetaData);
        selectOptionClauseParser = new MySQLSelectOptionClauseParser(lexerEngine);
        limitClauseParser = new MySQLLimitClauseParser(lexerEngine);
    }
    
    /**
     * 2. 解析具体逻辑 SQL 
     */
    protected void parseInternal(final SelectStatement selectStatement) {
        parseDistinct();
        parseSelectOption();
        // 解析待查询的列,获取数据库字段名和字段别名
        parseSelectList(selectStatement, getItems());
        // 解析获取逻辑表
        parseFrom(selectStatement);
        // 获取查询条件
        parseWhere(getShardingRule(), selectStatement, getItems());
        parseGroupBy(selectStatement);
        parseHaving();
        parseOrderBy(selectStatement);
        parseLimit(selectStatement);
        parseSelectRest();
    }
  • 创建 MySQLSelectClauseParserFacade 对象,初始化 DQL 语句门面解析器,为下面 parseInternal 做准备;
  • 解析具体逻辑 SQL,将语句按照 token 类型封装为 SelectStatement 对象,为后面 SQL 改写做准备。

总结

  1. 具体的词法分析并没有展开,主要是根据数据库类型获取对应的词法解析器,本文以 Mysql 为例,因此获取的是 MySQLLexer,接下来词法分析引擎 LexerEngine 获取到第一个有效的 token,并根据此 token 获取 SQL 解析器,本例中逻辑 SQL 第一个有效的 token 是 select,所以获得的解析器是 MySQLSelectParser
  2. 整体上,语句解析过程还是很简单的,就是将逻辑 SQL 按照 token 类型解析为 SQLStatement,为后面 SQL 路由改写做准备。不过,词法分析还是略复杂,有兴趣可自行 debug 跟踪学习。
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值