看了很长很长时间的建立连接的源码,NIO,SOCKET,连接这部分确实是我的短处,到现在都没看懂。
恩,换了一部分研究,就看了一下解析SQL的过程,这个过程在mycat的源码贡献者——http://www.hashzhang.com/frontend/html/index.html 这大兄弟这里是归类到了路由模块中。
既然人家是源码贡献者,站在巨人的肩膀上,顺着他的分类继续分析吧。
一个SQL语句从应用程序中发送到mycat上,mycat首先需要判断这个SQL语句是哪种。比如SELECT语句和SHOW语句就属于不同的大分类。
解析第一个词的方法在这,其实就是判断了一下第一个字符是哪个字母,然后case不同的方法进行继续判断。这里就不写多余的注释了,很简单。
public static int parse(String stmt) {
int length = stmt.length();
//FIX BUG FOR SQL SUCH AS /XXXX/SQL
int rt = -1;
for (int i = 0; i < length; ++i) {
switch (stmt.charAt(i)) {
case ' ':
case '\t':
case '\r':
case '\n':
continue;
case '/':
// such as /*!40101 SET character_set_client = @saved_cs_client
// */;
if (i == 0 && stmt.charAt(1) == '*' && stmt.charAt(2) == '!' && stmt.charAt(length - 2) == '*'
&& stmt.charAt(length - 1) == '/') {
return MYSQL_CMD_COMMENT;
}
case '#':
i = ParseUtil.comment(stmt, i);
if (i + 1 == length) {
return MYSQL_COMMENT;
}
continue;
case 'A':
case 'a':
rt = aCheck(stmt, i);
if (rt != OTHER) {
return rt;
}
continue;
case 'B':
case 'b':
rt = beginCheck(stmt, i);
if (rt != OTHER) {
return rt;
}
continue;
case 'C':
case 'c':
rt = commitOrCallCheckOrCreate(stmt, i);
if (rt != OTHER) {
return rt;
}
continue;
case 'D':
case 'd':
rt = deleteOrdCheck(stmt, i);
if (rt != OTHER) {
return rt;
}
continue;
case 'E':
case 'e':
rt = explainCheck(stmt, i);
if (rt != OTHER) {
return rt;
}
continue;
case 'I':
case 'i':
rt = insertCheck(stmt, i);
if (rt != OTHER) {
return rt;
}
continue;
case 'M':
case 'm':
rt = migrateCheck(stmt, i);
if (rt != OTHER) {
return rt;
}
continue;
case 'R':
case 'r':
rt = rCheck(stmt, i);
if (rt != OTHER) {
return rt;
}
continue;
case 'S':
case 's':
rt = sCheck(stmt, i);
if (rt != OTHER) {
return rt;
}
continue;
case 'T':
case 't':
rt = tCheck(stmt, i);
if (rt != OTHER) {
return rt;
}
continue;
case 'U':
case 'u':
rt = uCheck(stmt, i);
if (rt != OTHER) {
return rt;
}
continue;
case 'K':
case 'k':
rt = killCheck(stmt, i);
if (rt != OTHER) {
return rt;
}
continue;
case 'H':
case 'h':
rt = helpCheck(stmt, i);
if (rt != OTHER) {
return rt;
}
continue;
case 'L':
case 'l':
rt = lCheck(stmt, i);
if (rt != OTHER) {
return rt;
}
continue;
default:
continue;
}
}
return OTHER;
}
在判断第一个字母之后,会调用不同的方法进行继续判断,比如,第一个字母是S或者s,那么可能会有Select、show、start、savepoint等等语句,这样还需要进行第二个字母的判断。
static int sCheck(String stmt, int offset) {
if (stmt.length() > ++offset) {
switch (stmt.charAt(offset)) {
case 'A':
case 'a':
return savepointCheck(stmt, offset);
case 'E':
case 'e':
return seCheck(stmt, offset);
case 'H':
case 'h':
return showCheck(stmt, offset);
case 'T':
case 't':
return startCheck(stmt, offset);
default:
return OTHER;
}
}
return OTHER;
}
第二个字母之后有很多语句可以确定了,有的还不能确定,比如Select和Set,这个时候还需要继续判断,第三个判断我就不贴了,假装看完了,然后选择Select语句的处理方法看看吧。
static int selectCheck(String stmt, int offset) {
if (stmt.length() > offset + 4) {
char c1 = stmt.charAt(++offset);
char c2 = stmt.charAt(++offset);
char c3 = stmt.charAt(++offset);
char c4 = stmt.charAt(++offset);
if ((c1 == 'E' || c1 == 'e')
&& (c2 == 'C' || c2 == 'c')
&& (c3 == 'T' || c3 == 't')
&& (c4 == ' ' || c4 == '\t' || c4 == '\r' || c4 == '\n'
|| c4 == '/' || c4 == '#')) {
return (offset << 8) | SELECT;
}
}
return OTHER;
}
进入Select的语句检查后,会逐一判断select后面的几个字母,最后会将传入的表示第几个字符做一个移位运算再与表示SELECT的数值做一个与运算,然后返回去。其实并不理解为什么要这么做。。。
在判断了语句属于哪个大类之后,mycat会把语句交给专门处理这种大类语句的类来进行后续的处理。
public void query(String sql) {
ServerConnection c = this.source;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(new StringBuilder().append(c).append(sql).toString());
}
//解析SQL语句
int rs = ServerParse.parse(sql);
//与运算
int sqlType = rs & 255;
switch (sqlType) {
//explain sql
case ServerParse.EXPLAIN:
ExplainHandler.handle(sql, c, rs >>> 8);
break;
//explain2 datanode=? sql=?
case ServerParse.EXPLAIN2:
Explain2Handler.handle(sql, c, rs >>> 8);
break;
case ServerParse.SET:
SetHandler.handle(sql, c, rs >>> 8);
break;
case ServerParse.SHOW:
ShowHandler.handle(sql, c, rs >>> 8);
break;
case ServerParse.SELECT:
SelectHandler.handle(sql, c, rs >>> 8);
break;
后面太长了,就不截了,都是一样的调用某个大类语句的处理。
在进入Select语句处理类之后,mycat还会对第二个词进行分析,有些语句不需要访问后面的数据库返回数据,直接返回的是mycat模拟的mysql服务端的信息。
而需要处理的语句,就会执行路由查询,获取分片信息,执行数据库的查询。
public final class SelectHandler {
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;
case ServerParseSelect.SESSION_TX_READ_ONLY:
SelectTxReadOnly.response(c);
break;
default:
c.execute(stmt, ServerParse.SELECT);
}
}
}
最后的c.execute就是执行数据库查询,剩下的都是不用查询的- -
ok,这里就写到这了,大概是懂了SQL的解析过程了,其实可以看做是一个树形结构,广度优先搜索。从周更变成月更了。不应该呀,兄dei。