資料庫路由中介軟體MyCat - 原始碼篇(15)

weixin_34162695發表於2018-11-02

此文已由作者張鎬薪授權網易雲社群釋出。

歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。


public static void handle(String stmt, ServerConnection c, int offs) {
        int offset = offs;
        switch (ServerParseSelect.parse(stmt, offs)) {
        case ServerParseSelect.VERSION_COMMENT:
            SelectVersionComment.response(c);
            break;
        case ServerParseSelect.DATABASE:
            SelectDatabase.response(c);
            break;
        case ServerParseSelect.USER:
            SelectUser.response(c);
            break;
        case ServerParseSelect.VERSION:
            SelectVersion.response(c);
            break;
        case ServerParseSelect.SESSION_INCREMENT:
            SessionIncrement.response(c);
            break;
        case ServerParseSelect.SESSION_ISOLATION:
            SessionIsolation.response(c);
            break;
        case ServerParseSelect.LAST_INSERT_ID:
            // offset = ParseUtil.move(stmt, 0, "select".length());
            loop:for (int l=stmt.length(); offset < l; ++offset) {
                switch (stmt.charAt(offset)) {
                case ' ':
                    continue;
                case '/':
                case '#':
                    offset = ParseUtil.comment(stmt, offset);
                    continue;
                case 'L':
                case 'l':
                    break loop;
                }
            }
            offset = ServerParseSelect.indexAfterLastInsertIdFunc(stmt, offset);
            offset = ServerParseSelect.skipAs(stmt, offset);
            SelectLastInsertId.response(c, stmt, offset);
            break;
        case ServerParseSelect.IDENTITY:
            // offset = ParseUtil.move(stmt, 0, "select".length());
            loop:for (int l=stmt.length(); offset < l; ++offset) {
                switch (stmt.charAt(offset)) {
                case ' ':
                    continue;
                case '/':
                case '#':
                    offset = ParseUtil.comment(stmt, offset);
                    continue;
                case '@':
                    break loop;
                }
            }
            int indexOfAtAt = offset;
            offset += 2;
            offset = ServerParseSelect.indexAfterIdentity(stmt, offset);
            String orgName = stmt.substring(indexOfAtAt, offset);
            offset = ServerParseSelect.skipAs(stmt, offset);
            SelectIdentity.response(c, stmt, offset, orgName);
            break;
            case ServerParseSelect.SELECT_VAR_ALL:
                SelectVariables.execute(c,stmt);
                break;
        default:
            c.execute(stmt, ServerParse.SELECT);
        }
    }複製程式碼

下一步,ServerConnection類處理SQL語句


ServerConnection.java

public void execute(String sql, int type) {        //連線狀態檢查
        if (this.isClosed()) {
            LOGGER.warn("ignore execute ,server connection is closed " + this);            return;
        }        // 事務狀態檢查
        if (txInterrupted) {
            writeErrMessage(ErrorCode.ER_YES,                    "Transaction error, need to rollback." + txInterrputMsg);            return;
        }        // 檢查當前使用的DB
        String db = this.schema;        if (db == null) {
            db = SchemaUtil.detectDefaultDb(sql, type);            if (db == null) {
                writeErrMessage(ErrorCode.ERR_BAD_LOGICDB, "No MyCAT Database selected");                return;
            }
        }        // 相容PhpAdmin's, 支援對MySQL後設資料的模擬返回
        //// TODO: 2016/5/20 支援更多information_schema特性 
        if (ServerParse.SELECT == type 
                && db.equalsIgnoreCase("information_schema") ) {
            MysqlInformationSchemaHandler.handle(sql, this);            return;
        }        if (ServerParse.SELECT == type 
                && sql.contains("mysql") 
                && sql.contains("proc")) {

            SchemaUtil.SchemaInfo schemaInfo = SchemaUtil.parseSchema(sql);            if (schemaInfo != null 
                    && "mysql".equalsIgnoreCase(schemaInfo.schema)
                    && "proc".equalsIgnoreCase(schemaInfo.table)) {                // 相容MySQLWorkbench
                MysqlProcHandler.handle(sql, this);                return;
            }
        }

        SchemaConfig schema = MycatServer.getInstance().getConfig().getSchemas().get(db);        if (schema == null) {
            writeErrMessage(ErrorCode.ERR_BAD_LOGICDB,                    "Unknown MyCAT Database '" + db + "'");            return;
        }

        routeEndExecuteSQL(sql, type, schema);

    }複製程式碼

呼叫routeEndExecuteSQL方法,會解析出RouteResultSet。這步包含了SQL語義解析,SQL路由,SQL查詢優化,SQL語句改寫,全域性ID生成,最後,將解析出的RouteResultSet交給這個連結對應的session進行處理。 我們先分析SQL語義解析。看呼叫: ServerConnection.java

rrs = MycatServer
                    .getInstance()
                    .getRouterservice()
                    .route(MycatServer.getInstance().getConfig().getSystem(),
                            schema, type, sql, this.charset, this);複製程式碼

首先,關注下這個Routerservice是啥?在MyCat初始化時,會新建一個Routerservice(如之前配置模組中所講): MyCatServer.java

//路由計算初始化routerService = new RouteService(cacheService);複製程式碼

Routerservice結構: 其中sqlRouteCache和tableId2DataNodeCache是通過CacheService(MyCat裡面是ehcache做的快取)傳入的對於sql語句快取和tableid與後臺分片對應關係的快取。具體快取會在快取模組中講。

呼叫route方法解析出RouteResultSet

public RouteResultset route(SystemConfig sysconf, SchemaConfig schema,            int sqlType, String stmt, String charset, ServerConnection sc)
            throws SQLNonTransientException {
        RouteResultset rrs = null;
        String cacheKey = null;        /**
         *  SELECT 型別的SQL, 檢測
         */
        if (sqlType == ServerParse.SELECT) {
            cacheKey = schema.getName() + stmt;            
            rrs = (RouteResultset) sqlRouteCache.get(cacheKey);            if (rrs != null) {                return rrs;
            }
        }        /*!mycat: sql = select name from aa */
        /*!mycat: schema = test *///      boolean isMatchOldHint = stmt.startsWith(OLD_MYCAT_HINT);//      boolean isMatchNewHint = stmt.startsWith(NEW_MYCAT_HINT);//        if (isMatchOldHint || isMatchNewHint ) {
        int hintLength = RouteService.isHintSql(stmt);        if(hintLength != -1){            int endPos = stmt.indexOf("*/");            if (endPos > 0) {                
                // 用!mycat:內部的語句來做路由分析//                int hintLength = isMatchOldHint ? OLD_MYCAT_HINT.length() : NEW_MYCAT_HINT.length();
                String hint = stmt.substring(hintLength, endPos).trim();    

                int firstSplitPos = hint.indexOf(HINT_SPLIT);                
                if(firstSplitPos > 0 ){
                    Map hintMap=    parseHint(hint);
                    String hintType = (String) hintMap.get(MYCAT_HINT_TYPE);
                    String hintSql = (String) hintMap.get(hintType);                    if( hintSql.length() == 0 ) {
                        LOGGER.warn("comment int sql must meet :/*!mycat:type=value*/ or /*#mycat:type=value*/ or /*mycat:type=value*/: "+stmt);                        throw new SQLSyntaxErrorException("comment int sql must meet :/*!mycat:type=value*/ or /*#mycat:type=value*/ or /*mycat:type=value*/: "+stmt);
                    }
                    String realSQL = stmt.substring(endPos + "*/".length()).trim();

                    HintHandler hintHandler = HintHandlerFactory.getHintHandler(hintType);                    if( hintHandler != null ) {    

                        if ( hintHandler instanceof  HintSQLHandler) {                            
                              /**
                             * 修復 註解SQL的 sqlType 與 實際SQL的 sqlType 不一致問題, 如: hint=SELECT,real=INSERT
                             * fixed by zhuam
                             */
                            int hintSqlType = ServerParse.parse( hintSql ) & 0xff;     
                            rrs = hintHandler.route(sysconf, schema, sqlType, realSQL, charset, sc, tableId2DataNodeCache, hintSql,hintSqlType,hintMap);

                        } else {                            
                            rrs = hintHandler.route(sysconf, schema, sqlType, realSQL, charset, sc, tableId2DataNodeCache, hintSql,sqlType,hintMap);
                        }

                    }else{
                        LOGGER.warn("TODO , support hint sql type : " + hintType);
                    }

                }else{//fixed by runfriends@126.com
                    LOGGER.warn("comment in sql must meet :/*!mycat:type=value*/ or /*#mycat:type=value*/ or /*mycat:type=value*/: "+stmt);                    throw new SQLSyntaxErrorException("comment in sql must meet :/*!mcat:type=value*/ or /*#mycat:type=value*/ or /*mycat:type=value*/: "+stmt);
                }
            }
        } else {
            stmt = stmt.trim();
            rrs = RouteStrategyFactory.getRouteStrategy().route(sysconf, schema, sqlType, stmt,
                    charset, sc, tableId2DataNodeCache);
        }        if (rrs != null && sqlType == ServerParse.SELECT && rrs.isCacheAble()) {
            sqlRouteCache.putIfAbsent(cacheKey, rrs);
        }        return rrs;
    }複製程式碼

由於註解處理和sql解析有重疊,而且註解處理一直程式碼不穩定,所以,這裡不涉及。只說sql正常解析的步驟



免費體驗雲安全(易盾)內容安全、驗證碼等服務

更多網易技術、產品、運營經驗分享請點選



相關文章:
【推薦】 掃臉動畫
【推薦】 selenium下拉框踩坑埋坑
【推薦】 大資料、資料探勘在交通領域的應用


相關文章