数据库路由中间件MyCat - 源代码篇(17)
此文已由作者张镐薪授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
调用processInsert(sc,schema,sqlType,origSQL,tableName,primaryKey):
public static boolean processInsert(ServerConnection sc,SchemaConfig schema, int sqlType,String origSQL,String tableName,String primaryKey) throws SQLNonTransientException { int firstLeftBracketIndex = origSQL.indexOf("("); int firstRightBracketIndex = origSQL.indexOf(")");
String upperSql = origSQL.toUpperCase(); int valuesIndex = upperSql.indexOf("VALUES"); int selectIndex = upperSql.indexOf("SELECT"); int fromIndex = upperSql.indexOf("FROM"); //屏蔽insert into table1 select * from table2语句
if(firstLeftBracketIndex < 0) {
String msg = "invalid sql:" + origSQL;
LOGGER.warn(msg); throw new SQLNonTransientException(msg);
} //屏蔽批量插入
if(selectIndex > 0 &&fromIndex>0&&selectIndex>firstRightBracketIndex&&valuesIndex<0) {
String msg = "multi insert not provided" ;
LOGGER.warn(msg); throw new SQLNonTransientException(msg);
} //插入语句必须提供列结构,因为MyCat默认对于表结构无感知
if(valuesIndex + "VALUES".length() <= firstLeftBracketIndex) { throw new SQLSyntaxErrorException("insert must provide ColumnList");
} //如果主键不在插入语句的fields中,则需要进一步处理
boolean processedInsert=!isPKInFields(origSQL,primaryKey,firstLeftBracketIndex,firstRightBracketIndex); if(processedInsert){
processInsert(sc,schema,sqlType,origSQL,tableName,primaryKey,firstLeftBracketIndex+1,origSQL.indexOf('(',firstRightBracketIndex)+1);
} return processedInsert;
}
对于主键不在插入语句的fields中的SQL,需要改写。比如hotnews主键为id,插入语句为:
insert into hotnews(title) values('aaa');
需要改写成:
insert into hotnews(id, title) values(next value for MYCATSEQ_hotnews,'aaa');
这个在下面这个函数实现:
private static void processInsert(ServerConnection sc, SchemaConfig schema, int sqlType, String origSQL,
String tableName, String primaryKey, int afterFirstLeftBracketIndex, int afterLastLeftBracketIndex) { int primaryKeyLength = primaryKey.length(); int insertSegOffset = afterFirstLeftBracketIndex;
String mycatSeqPrefix = "next value for MYCATSEQ_"; int mycatSeqPrefixLength = mycatSeqPrefix.length(); int tableNameLength = tableName.length(); char[] newSQLBuf = new char[origSQL.length() + primaryKeyLength + mycatSeqPrefixLength + tableNameLength + 2];
origSQL.getChars(0, afterFirstLeftBracketIndex, newSQLBuf, 0);
primaryKey.getChars(0, primaryKeyLength, newSQLBuf, insertSegOffset);
insertSegOffset += primaryKeyLength;
newSQLBuf[insertSegOffset] = ',';
insertSegOffset++;
origSQL.getChars(afterFirstLeftBracketIndex, afterLastLeftBracketIndex, newSQLBuf, insertSegOffset);
insertSegOffset += afterLastLeftBracketIndex - afterFirstLeftBracketIndex;
mycatSeqPrefix.getChars(0, mycatSeqPrefixLength, newSQLBuf, insertSegOffset);
insertSegOffset += mycatSeqPrefixLength;
tableName.getChars(0, tableNameLength, newSQLBuf, insertSegOffset);
insertSegOffset += tableNameLength;
newSQLBuf[insertSegOffset] = ',';
insertSegOffset++;
origSQL.getChars(afterLastLeftBracketIndex, origSQL.length(), newSQLBuf, insertSegOffset);
processSQL(sc, schema, new String(newSQLBuf), sqlType);
}
最后的processSQL(sc, schema, new String(newSQLBuf), sqlType);是将语句放入执行队列:
这里MyCat考虑NIO线程吞吐量以及全局ID生成线程安全的问题,使用如下流程执行需要全局ID的SQL insert语句。
processSQL(sc, schema, new String(newSQLBuf), sqlType):
SessionSQLPair sessionSQLPair = new SessionSQLPair(sc.getSession2(), schema, sql, sqlType);
MycatServer.getInstance().getSequnceProcessor().addNewSql(sessionSQLPair);
5.4 DDL语句路由
可以分为两步,整体源代码:
public static RouteResultset routeToDDLNode(RouteResultset rrs, int sqlType, String stmt,SchemaConfig schema) throws SQLSyntaxErrorException {
stmt = getFixedSql(stmt);
String tablename = "";
final String upStmt = stmt.toUpperCase(); if(upStmt.startsWith("CREATE")){ if (upStmt.contains("CREATE INDEX ")){
tablename = RouterUtil.getTableName(stmt, RouterUtil.getCreateIndexPos(upStmt, 0));
}else tablename = RouterUtil.getTableName(stmt, RouterUtil.getCreateTablePos(upStmt, 0));
}else if(upStmt.startsWith("DROP")){ if (upStmt.contains("DROP INDEX ")){
tablename = RouterUtil.getTableName(stmt, RouterUtil.getDropIndexPos(upStmt, 0));
}else tablename = RouterUtil.getTableName(stmt, RouterUtil.getDropTablePos(upStmt, 0));
}else if(upStmt.startsWith("ALTER")){
tablename = RouterUtil.getTableName(stmt, RouterUtil.getAlterTablePos(upStmt, 0));
}else if (upStmt.startsWith("TRUNCATE")){
tablename = RouterUtil.getTableName(stmt, RouterUtil.getTruncateTablePos(upStmt, 0));
}
tablename = tablename.toUpperCase(); if (schema.getTables().containsKey(tablename)){ if(ServerParse.DDL==sqlType){
List<String> dataNodes = new ArrayList<>();
Map<String, TableConfig> tables = schema.getTables();
TableConfig tc; if (tables != null && (tc = tables.get(tablename)) != null) {
dataNodes = tc.getDataNodes();
}
Iterator<String> iterator1 = dataNodes.iterator(); int nodeSize = dataNodes.size();
RouteResultsetNode[] nodes = new RouteResultsetNode[nodeSize]; for(int i=0;i<nodeSize;i++){
String name = iterator1.next();
nodes[i] = new RouteResultsetNode(name, sqlType, stmt);
}
rrs.setNodes(nodes);
} return rrs;
}else if(schema.getDataNode()!=null){ //默认节点ddl
RouteResultsetNode[] nodes = new RouteResultsetNode[1];
nodes[0] = new RouteResultsetNode(schema.getDataNode(), sqlType, stmt);
rrs.setNodes(nodes); return rrs;
} //both tablename and defaultnode null
LOGGER.error("table not in schema----"+tablename); throw new SQLSyntaxErrorException("op table not in schema----"+tablename);
}
首先,获取表名,步骤如下:
拿一个获取表名的函数举例:
/**
* 获取语句中前关键字位置和占位个数表名位置
*
* @param upStmt
* 执行语句
* @param start
* 开始位置
* @return int[]关键字位置和占位个数
* @author aStoneGod
*/public static int[] getCreateIndexPos(String upStmt, int start) {
String token1 = "CREATE ";
String token2 = " INDEX ";
String token3 = " ON "; int createInd = upStmt.indexOf(token1, start); int idxInd = upStmt.indexOf(token2, start); int onInd = upStmt.indexOf(token3, start); // 既包含CREATE又包含INDEX,且CREATE关键字在INDEX关键字之前, 且包含ON...
if (createInd >= 0 && idxInd > 0 && idxInd > createInd && onInd > 0 && onInd > idxInd) { return new int[] {onInd , token3.length() };
} else { return new int[] { -1, token2.length() };// 不满足条件时,只关注第一个返回值为-1,第二个任意
}
}
然后,根据表名获取配置进行路由:
默认语句路由
对于有默认节点的schema,且不是show, describe, select @@之类的语句,则路由到默认的节点上。
对于show, describe, select @@之类的语句,利用查询信息路由方法算出路由。接下来,取一个举例,对于Show语句:analyseShowSQL(schema, rrs, stmt)方法
5.5 AST语义解析路由
首先我们看一下MySQL的SQL解析步骤(硬解析和软解析):MyCat的机制,仿照MySQL的,可以总结为:这里我们可以总结一个优化思路,就是通过仿照MySQL物理优化原理(定时更新表配置,报表信息),来做进一步MyCat查询的优化。语义解析基本过程:
1.词法分析(一般抽象都叫Lexer):不同的关键词有不同的含义
select concat(id,'_',name),value from student where value>60 order by value
词法分析的输出,就是一句带上词义的语句:
(select: Keyword) (concat: Keyword)((: LB)…… (from: keyword) (student: identifier)
2.语法分析:
分析关键词之间的联系,生成表达式(expression)
基本语法正确性判断(比如from这个keyword之后必须紧跟一个表名(就是一个identifier))
3.生成AST语意树(完整解析的statement)根据MyCat权威指南,DruidParser比其他Parser快很多很多。
快在哪里呢?主要是抽象静态化的粒度,拿jsqlparser和druidparser对比。
这两个parser都遵从了上面的步骤,对于词(lexer),表达式(expression)和语句AST(statement)都有抽象。
但是对于语句AST(statement)的抽象, DruidParser做的粒度更细。如下图对于Alter语句的对比:所以,不难推测为啥DruidParser快了
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 如何解决在线网页挂载本地样式的问题
【推荐】 如何作缺陷分析
数据库路由中间件MyCat - 源代码篇(17)的更多相关文章
- 数据库路由中间件MyCat - 源代码篇(1)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 进入了源代码篇,我们先从整体入手,之后拿一个简单流程前端连接建立与认证作为例子,理清代码思路和设计模式.然后 ...
- 数据库路由中间件MyCat - 源代码篇(13)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 4.配置模块 4.2 schema.xml 接上一篇,接下来载入每个schema的配置(也就是每个MyCat ...
- 数据库路由中间件MyCat - 源代码篇(7)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 3. 连接模块 3.4 FrontendConnection前端连接 构造方法: public Fronte ...
- 数据库路由中间件MyCat - 源代码篇(15)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. public static void handle(String stmt, ServerConnectio ...
- 数据库路由中间件MyCat - 源代码篇(14)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 对于表的dataNode对应关系,有个特殊配置即类似dataNode="distributed(d ...
- 数据库路由中间件MyCat - 源代码篇(4)
此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 2. 前端连接建立与认证 Title:MySql连接建立以及认证过程client->MySql:1.T ...
- 数据库路由中间件MyCat - 源代码篇(2)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 2. 前端连接建立与认证 Title:MySql连接建立以及认证过程client->MySql:1.T ...
- 数据库路由中间件MyCat - 源代码篇(16)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 5. 路由模块 真正取得RouteResultset的步骤:AbstractRouteStrategy的ro ...
- 数据库路由中间件MyCat - 源代码篇(10)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 3. 连接模块 3.5 后端连接 3.5.2 后端连接获取与维护管理 还是那之前的流程, st=>st ...
随机推荐
- coreseek中文搜索
coreseek的安装和使用 准备软件包 coreseek-3.2.14.tar.gz 其他汁源 coreseek中文索引-示例文件.zip sphinx配置文件详解.txt 1.安装组件 yum - ...
- Rocketmq消息持久化
本文编写,参考:https://my.oschina.net/bieber/blog/725646 producer Send()的Message最终将由broker处理,处理类为:SendMessa ...
- 《CSS权威指南(第三版)》---第五章 字体
这章主要的内容有: 1.字体:一般使用一种通用的字体. 2.字体加粗:一般从数字100 -900 . 3.字体大小:font-size 4.拉伸和调整字体:font-stretch 5.调整字体大小: ...
- LightOJ - 1284 Lights inside 3D Grid —— 期望
题目链接:https://vjudge.net/problem/LightOJ-1284 1284 - Lights inside 3D Grid PDF (English) Statistic ...
- C# 关于线程锁lock的使用方法
C# 关于线程锁lock的使用方法 原创 2016年09月02日 10:07:05 标签: c# / 线程 / 锁 / lock 11937 在多线程编程中,可能会有许多线程并发的执行一段代码(代码块 ...
- FastJson 输出值 首字母大小写问题
解决方案: 1. 如果你的项目由多个模块且为分布式部署, 则可考虑使用设置System.property 2. 一般只是极少数的代码出现此情况, 那么建议直接在你的单例Service初始化时, 在静态 ...
- python列表推导式的if-else
a=[i for i in range(10) if i%2==0]print(a)a=[i if i%2==0 else 'qi' for i in range(10)]print(a) 结果:[0 ...
- 分享知识-快乐自己:论 Mybatis中的关联关系(一对多,多对一,多对多)
论:一对多:(举例一个省有多个市)就是实体类中有(市)类型集合属性:多对一:(多个市有一个共同的省)就是类中有(省)类型的属性.下面来介绍:一对一.多对一的使用方式. 一对多方: package ml ...
- 【HDU 6126】Give out candies 最小割
题意 有$n$个小朋友,给每个人分$1~m$个糖果,有k个限制 限制形如$(x,y,z)$ 表示第$x$个人分到的糖数减去第$y$个人分到的糖数不大于$z$,给第$i$个人$j$颗糖获 ...
- loading bar
上面的loading条,想到的办法是用两个半圆覆盖实现,结果也就这么做了,可是明明一个圆就可以的,哎智商堪忧... <!DOCTYPE html> <html lang=" ...