前面,我们已经讲过了Mycat如何判断sql类型,然后针对不同类型的sql就行不同的处理【Mycat原理解析-SQL语句的处理】,下面,我们来看看其中的explain语句,Mycat是怎么处理的。
Mycat提供的EXPLAIN语句并不是用来查看执行计划的,而是用来查看路由结果的,如果要查询真正的执行计划,拿到路由结果里面的sql语句,到具体的实例上面查看就行了。例如,下面的sql语句,就被路由到了8个库。
下面,我们来看看这部分的源码ExplainHandler.java。
static {
fields[0] = PacketUtil.getField("DATA_NODE",
Fields.FIELD_TYPE_VAR_STRING);
fields[1] = PacketUtil.getField("SQL", Fields.FIELD_TYPE_VAR_STRING);
}
public static void handle(String stmt, ServerConnection c, int offset) {
//获取explain后面的sql
stmt = stmt.substring(offset).trim();
//获取路由结果
RouteResultset rrs = getRouteResultset(c, stmt);
if (rrs == null) {
return;
}
ByteBuffer buffer = c.allocate();
// 写入头部
ResultSetHeaderPacket header = PacketUtil.getHeader(FIELD_COUNT);
byte packetId = header.packetId;
buffer = header.write(buffer, c,true);
// 写入返回的字段【也就是示例中的第一行DATA_NODE、SQL】
for (FieldPacket field : fields) {
field.packetId = ++packetId;
buffer = field.write(buffer, c,true);
}
// write eof
EOFPacket eof = new EOFPacket();
eof.packetId = ++packetId;
buffer = eof.write(buffer, c,true);
// 写入路由结果【也就是dataNode及对应的sql】
RouteResultsetNode[] rrsn = rrs.getNodes();
for (RouteResultsetNode node : rrsn) {
RowDataPacket row = getRow(node, c.getCharset());
row.packetId = ++packetId;
buffer = row.write(buffer, c,true);
}
// write last eof
EOFPacket lastEof = new EOFPacket();
lastEof.packetId = ++packetId;
buffer = lastEof.write(buffer, c,true);
// post write
c.write(buffer);
}
private static RouteResultset getRouteResultset(ServerConnection c,
String stmt) {
String db = c.getSchema();
int sqlType = ServerParse.parse(stmt) & 0xff;
if (db == null) {
db = SchemaUtil.detectDefaultDb(stmt, sqlType);
if(db==null){
c.writeErrMessage(ErrorCode.ER_NO_DB_ERROR, "No database selected");
return null;
}
}
//获取逻辑库对应的配置信息
SchemaConfig schema = MycatServer.getInstance().getConfig()
.getSchemas().get(db);
//如果逻辑库不存在,返回错误信息
if (schema == null) {
c.writeErrMessage(ErrorCode.ER_BAD_DB_ERROR, "Unknown database '"+ db + "'");
return null;
}
try {
//不支持insert语句并且使用Mycat生成id的sql语句。这个很好理解,因为无法无法路由
if(ServerParse.INSERT==sqlType&&isMycatSeq(stmt, schema){
c.writeErrMessage(ErrorCode.ER_PARSE_ERROR, "insert sql using mycat seq,you must provide primaryKey value for explain");
return null;
}
SystemConfig system = MycatServer.getInstance().getConfig().getSystem();
//路由
return MycatServer.getInstance().getRouterservice()
.route(system,schema, sqlType, stmt, c.getCharset(), c);
} catch (Exception e) {
StringBuilder s = new StringBuilder();
logger.warn(s.append(c).append(stmt).toString()+" error:"+ e);
String msg = e.getMessage();
c.writeErrMessage(ErrorCode.ER_PARSE_ERROR, msg == null ? e
.getClass().getSimpleName() : msg);
return null;
}
}
有关路由部分的原理,会在后续的文章中进行剖析。