此文已由作者张镐薪授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。

5. 路由模块

真正取得RouteResultset的步骤:AbstractRouteStrategy的route方法:对应源代码:

public RouteResultset route(SystemConfig sysConfig, SchemaConfig schema, int sqlType, String origSQL,
            String charset, ServerConnection sc, LayerCachePool cachePool) throws SQLNonTransientException {    /**
     * 处理一些路由之前的逻辑
     * 全局序列号,父子表插入
     */
    if ( beforeRouteProcess(schema, sqlType, origSQL, sc) )        return null;    /**
     * SQL 语句拦截
     */
    String stmt = MycatServer.getInstance().getSqlInterceptor().interceptSQL(origSQL, sqlType);    if (origSQL != stmt && LOGGER.isDebugEnabled()) {
        LOGGER.debug("sql intercepted to " + stmt + " from " + origSQL);
    }    //对应schema标签checkSQLschema属性,把表示schema的字符去掉
    if (schema.isCheckSQLSchema()) {
        stmt = RouterUtil.removeSchema(stmt, schema.getName());
    }     RouteResultset rrs = new RouteResultset(stmt, sqlType);    /**
     * 优化debug loaddata输出cache的日志会极大降低性能
     */
    if (LOGGER.isDebugEnabled() && origSQL.startsWith(LoadData.loadDataHint)) {
        rrs.setCacheAble(false);
    }       /**
        * rrs携带ServerConnection的autocommit状态用于在sql解析的时候遇到
        * select ... for update的时候动态设定RouteResultsetNode的canRunInReadDB属性
        */
    if (sc != null ) {
        rrs.setAutocommit(sc.isAutocommit());
    }    /**
     * DDL 语句的路由
     */
    if (ServerParse.DDL == sqlType) {        return RouterUtil.routeToDDLNode(rrs, sqlType, stmt, schema);
    }    /**
     * 检查是否有分片
     */
    if (schema.isNoSharding() && ServerParse.SHOW != sqlType) {
        rrs = RouterUtil.routeToSingleNode(rrs, schema.getDataNode(), stmt);
    } else {
        RouteResultset returnedSet = routeSystemInfo(schema, sqlType, stmt, rrs);        if (returnedSet == null) {
            rrs = routeNormalSqlWithAST(schema, stmt, rrs, charset, cachePool);
        }
    }    return rrs;
}

5.3 路由之前的逻辑 - 判断子表插入以及全局序列号的生成:

AbstractRouteStrategy.java

/**
 * 路由之前必要的处理
 * 主要是全局序列号插入,还有子表插入
 */private boolean beforeRouteProcess(SchemaConfig schema, int sqlType, String origSQL, ServerConnection sc)
        throws SQLNonTransientException {    return RouterUtil.processWithMycatSeq(schema, sqlType, origSQL, sc)
            || (sqlType == ServerParse.INSERT && RouterUtil.processERChildTable(schema, origSQL, sc))
            || (sqlType == ServerParse.INSERT && RouterUtil.processInsert(schema, sqlType, origSQL, sc));
}

这里利用了Java的一个特性,||表达式,前半部分如果为真,则后半部分不会被执行。首先执行RouterUtil.processWithMycatSeq(schema, sqlType, origSQL, sc),这个方法是判断是否是显示使用全局序列号的sql语句,比如像:insert into table1(id,name) values(next value for MYCATSEQ_GLOBAL,‘test’);对于这样的语句处理是先将改写next value for MYCATSEQ_GLOBAL 为调用全局ID生成的ID,之后进入AST语句解析路由

如果不是,则执行(sqlType == ServerParse.INSERT && RouterUtil.processERChildTable(schema, origSQL, sc)),这个方法判断是否是子表插入:部分代码:

String tableName = StringUtil.getTableName(origSQL).toUpperCase();final TableConfig tc = schema.getTables().get(tableName);//判断是否为子表,如果不是,只会返回falseif (null != tc && tc.isChildTable()) {final RouteResultset rrs = new RouteResultset(origSQL, ServerParse.INSERT);
String joinKey = tc.getJoinKey();//因为是Insert语句,用MySqlInsertStatement进行parseMySqlInsertStatement insertStmt = (MySqlInsertStatement) (new MySqlStatementParser(origSQL)).parseInsert();
......

这里注意,所有类型的SQL语句都有druid对应的SQLparser,比如说这里的插入语句就用MySqlInsertStatement解析。druidparser在这节先不讲,会在 AST语义解析路由中详细讲述。

接上面代码:

//判断条件完整性,取得解析后语句列中的joinkey列的index
    int joinKeyIndex = getJoinKeyIndex(insertStmt.getColumns(), joinKey);    if (joinKeyIndex == -1) {
        String inf = "joinKey not provided :" + tc.getJoinKey() + "," + insertStmt;
        LOGGER.warn(inf);        throw new SQLNonTransientException(inf);
    }    //子表不支持批量插入
    if (isMultiInsert(insertStmt)) {
        String msg = "ChildTable multi insert not provided";
        LOGGER.warn(msg);        throw new SQLNonTransientException(msg);
    }    //取得joinkey的值
    String joinKeyVal = insertStmt.getValues().getValues().get(joinKeyIndex).toString();     String sql = insertStmt.toString();    // try to route by ER parent partion key
    //如果是二级子表(父表不再有父表),并且分片字段正好是joinkey字段,调用routeByERParentKey
    RouteResultset theRrs = RouterUtil.routeByERParentKey(sc, schema, ServerParse.INSERT, sql, rrs, tc, joinKeyVal);    if (theRrs != null) {        boolean processedInsert=false;        //判断是否需要全局序列号
              if ( sc!=null && tc.isAutoIncrement()) {
                  String primaryKey = tc.getPrimaryKey();
                  processedInsert=processInsert(sc,schema,ServerParse.INSERT,sql,tc.getName(),primaryKey);
              }              if(processedInsert==false){
                  rrs.setFinishedRoute(true);
                  sc.getSession2().execute(rrs, ServerParse.INSERT);
              }        return true;
    }    // route by sql query root parent's datanode
    //如果不是二级子表或者分片字段不是joinKey字段结果为空,则启动异步线程去后台分片查询出datanode
    //只要查询出上一级表的parentkey字段的对应值在哪个分片即可
    final String findRootTBSql = tc.getLocateRTableKeySql().toLowerCase() + joinKeyVal;    if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("find root parent's node sql " + findRootTBSql);
    }     ListenableFuture<String> listenableFuture = MycatServer.getInstance().
            getListeningExecutorService().submit(new Callable<String>() {        @Override
        public String call() throws Exception {
            FetchStoreNodeOfChildTableHandler fetchHandler = new FetchStoreNodeOfChildTableHandler();            return fetchHandler.execute(schema.getName(), findRootTBSql, tc.getRootParent().getDataNodes());
        }
    });     Futures.addCallback(listenableFuture, new FutureCallback<String>() {        @Override
        public void onSuccess(String result) {            //结果为空,证明上一级表中不存在那条记录,失败
            if (Strings.isNullOrEmpty(result)) {
                StringBuilder s = new StringBuilder();
                LOGGER.warn(s.append(sc.getSession2()).append(origSQL).toString() +                        " err:" + "can't find (root) parent sharding node for sql:" + origSQL);
                sc.writeErrMessage(ErrorCode.ER_PARSE_ERROR, "can't find (root) parent sharding node for sql:" + origSQL);                return;
            }            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("found partion node for child table to insert " + result + " sql :" + origSQL);
            }            //找到分片,进行插入(和其他的一样,需要判断是否需要全局自增ID)
            boolean processedInsert=false;                  if ( sc!=null && tc.isAutoIncrement()) {                      try {
                          String primaryKey = tc.getPrimaryKey();
                    processedInsert=processInsert(sc,schema,ServerParse.INSERT,origSQL,tc.getName(),primaryKey);
                } catch (SQLNonTransientException e) {
                    LOGGER.warn("sequence processInsert error,",e);
                    sc.writeErrMessage(ErrorCode.ER_PARSE_ERROR , "sequence processInsert error," + e.getMessage());
                }
                  }                  if(processedInsert==false){
                      RouteResultset executeRrs = RouterUtil.routeToSingleNode(rrs, result, origSQL);
                      sc.getSession2().execute(executeRrs, ServerParse.INSERT);
                  }         }        @Override
        public void onFailure(Throwable t) {
            StringBuilder s = new StringBuilder();
            LOGGER.warn(s.append(sc.getSession2()).append(origSQL).toString() +                    " err:" + t.getMessage());
            sc.writeErrMessage(ErrorCode.ER_PARSE_ERROR, t.getMessage() + " " + s.toString());
        }
    }, MycatServer.getInstance().
            getListeningExecutorService());    return true;
}return false;

如果返回false,则继续执行(sqlType == ServerParse.INSERT && RouterUtil.processInsert(schema, sqlType, origSQL, sc))
这个是处理一般的SQL插入语句,将其中的自增主键字段的值改写成内置的全局ID生成器生成的id。RouterUtil.java:

public static boolean processInsert(SchemaConfig schema, int sqlType,
                                        String origSQL, ServerConnection sc) throws SQLNonTransientException {
    String tableName = StringUtil.getTableName(origSQL).toUpperCase();
    TableConfig tableConfig = schema.getTables().get(tableName);    boolean processedInsert=false;    //判断是有自增字段
    if (null != tableConfig && tableConfig.isAutoIncrement()) {
        String primaryKey = tableConfig.getPrimaryKey();
        processedInsert=processInsert(sc,schema,sqlType,origSQL,tableName,primaryKey);
    }    return processedInsert;
}

免费体验云安全(易盾)内容安全、验证码等服务

更多网易技术、产品、运营经验分享请点击

相关文章:
【推荐】 为何要在网站上设置的验证码
【推荐】 如何玩转基于风险的测试

数据库路由中间件MyCat - 源代码篇(16)的更多相关文章

  1. 数据库路由中间件MyCat - 源代码篇(1)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 进入了源代码篇,我们先从整体入手,之后拿一个简单流程前端连接建立与认证作为例子,理清代码思路和设计模式.然后 ...

  2. 数据库路由中间件MyCat - 源代码篇(13)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 4.配置模块 4.2 schema.xml 接上一篇,接下来载入每个schema的配置(也就是每个MyCat ...

  3. 数据库路由中间件MyCat - 源代码篇(7)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 3. 连接模块 3.4 FrontendConnection前端连接 构造方法: public Fronte ...

  4. 数据库路由中间件MyCat - 源代码篇(15)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. public static void handle(String stmt, ServerConnectio ...

  5. 数据库路由中间件MyCat - 源代码篇(17)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 调用processInsert(sc,schema,sqlType,origSQL,tableName,pr ...

  6. 数据库路由中间件MyCat - 源代码篇(14)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 对于表的dataNode对应关系,有个特殊配置即类似dataNode="distributed(d ...

  7. 数据库路由中间件MyCat - 源代码篇(4)

    此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 2. 前端连接建立与认证 Title:MySql连接建立以及认证过程client->MySql:1.T ...

  8. 数据库路由中间件MyCat - 源代码篇(2)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 2. 前端连接建立与认证 Title:MySql连接建立以及认证过程client->MySql:1.T ...

  9. 数据库路由中间件MyCat - 源代码篇(10)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 3. 连接模块 3.5 后端连接 3.5.2 后端连接获取与维护管理 还是那之前的流程, st=>st ...

随机推荐

  1. Oracle的权限管理

    --授予系统权限语法 grant system_privilege | all privileges --可以设置select any dictionary权限之外的索引系统权限 to {user i ...

  2. pugixml 1.9 manual解读(部分)

    Plain character data nodes (node_pcdata) represent plain text in XML. PCDATA nodes have a value, but ...

  3. 常用的Hql语句(转)

    原文地址:http://blog.csdn.net/v123411739/article/details/28644007     尊重原创,请访问原文地址 // HQL: Hibernate Que ...

  4. Django 文件下载功能

    def file_download(request): con= MySQLdb.connect(host='192.168.xxx.xxx',user='root',passwd='xxxx',db ...

  5. hdu5606 tree (并查集)

    tree Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submis ...

  6. codeforces 633A A. Ebony and Ivory(暴力)

    A. Ebony and Ivory time limit per test 2 seconds memory limit per test 256 megabytes input standard ...

  7. WingIDE用法笔记

    注释代码块 方法一:  ''' 被注释的代码块 '''   方法二:  选中要注释的代码块后 Ctrl + /,则选中的每一行都被# ,   用这种方法注释的代码,用Shift + Ctrl + / ...

  8. Failed to install xxxx.apk on device 'emulator-5554!

    异常信息:[HelloAndroid] Performing com.example.helloandroid.HelloAndroid activity launch[HelloAndroid] U ...

  9. 如何用Mendeley引用目标期刊要求的参考文献格式

    如果我们要向目标的杂志期刊投稿,则需要采用该期刊的参考文献格式.我用的mendeley管理文献,不收费且使用方便.那么,我们如何用mendeley引用目标期刊的参考文献呢?以Applied energ ...

  10. 3.1 第一个场景 HelloWorldScene

    HelloWorldScene.h #ifndef __HELLOWORLD_SCENE_H__ #define __HELLOWORLD_SCENE_H__ #include "cocos ...