数据库路由中间件MyCat - 源代码篇(15)
此文已由作者张镐薪授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | 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正常解析的步骤
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 AndroidTV开发(3)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core内存结构体系(Windows环境)底层原理浅谈
· C# 深度学习:对抗生成网络(GAN)训练头像生成模型
· .NET 适配 HarmonyOS 进展
· .NET 进程 stackoverflow异常后,还可以接收 TCP 连接请求吗?
· SQL Server统计信息更新会被阻塞或引起会话阻塞吗?
· 传国玉玺易主,ai.com竟然跳转到国产AI
· 本地部署 DeepSeek:小白也能轻松搞定!
· 自己如何在本地电脑从零搭建DeepSeek!手把手教学,快来看看! (建议收藏)
· 我们是如何解决abp身上的几个痛点
· 普通人也能轻松掌握的20个DeepSeek高频提示词(2025版)