源码分析MyCat----优化篇之支持ER分片多语句插入(基于1

//结果为空,证明上一级表中不存在那条记录,失败

if (Strings.isNullOrEmpty(result)) {

StringBuilder s = new StringBuilder();

LOGGER.warn(s.append(sc.getSession2()).append(origSQL).toString() +

" err:" + “can’t find (root) parent sharding node for sql:” + origSQL);

if(!sc.isAutocommit()) { // 处于事务下失败, 必须回滚

sc.setTxInterrupt(“can’t find (root) parent sharding node for sql:” + origSQL);

}

sc.writeErrMessage(ErrorCode.ER_PARSE_ERROR, “can’t find (root) parent sharding node for sql:” + origSQL);

return;

}

if (LOGGER.isDebugEnabled()) {

LOGGER.debug(“found partion node for child table to insert " + result + " sql :” + origSQL);

}

//找到分片,进行插入(和其他的一样,需要判断是否需要全局自增ID)

boolean processedInsert=false;

if ( sc!=null && tc.isAutoIncrement()) {

try {

String primaryKey = tc.getPrimaryKey();

processedInsert=processInsert(sc,schema,ServerParse.INSERT,origSQL,tc.getName(),primaryKey);

} catch (SQLNonTransientException e) {

LOGGER.warn(“sequence processInsert error,”,e);

sc.writeErrMessage(ErrorCode.ER_PARSE_ERROR , “sequence processInsert error,” + e.getMessage());

}

}

if(processedInsert==false){

RouteResultset executeRrs = RouterUtil.routeToSingleNode(rrs, result, origSQL);

sc.getSession2().execute(executeRrs, ServerParse.INSERT);

}

}

@Override

public void onFailure(Throwable t) {

StringBuilder s = new StringBuilder();

LOGGER.warn(s.append(sc.getSession2()).append(origSQL).toString() +

" err:" + t.getMessage());

sc.writeErrMessage(ErrorCode.ER_PARSE_ERROR, t.getMessage() + " " + s.toString());

}

}, MycatServer.getInstance().

getListeningExecutorService());

return true;

}

return false;

}

问题出现在这里,,这里把传入的SQL固定认为只会有一条insert into 语句,导致一簇insert into语句,只会第一条语句生效。接下来给出修复代码,并分析一下该方法。

给出的修复代码如下:

/**

  • 该方法,返回是否是ER字表

  • @param schema

  • @param origSQL

  • @param sc

  • @return

  • @throws SQLNonTransientException

  • 备注说明:

  • edit by ding.w at 2017.4.28, 主要处理 CLIENT_MULTI_STATEMENTS(insert into ; insert into)的情况
    
  • 目前仅支持mysql,并COM_QUERY请求包中的所有insert语句要么全部是er表,要么全部不是
    

*/

public static boolean processERChildTable(final SchemaConfig schema, final String origSQL,

final ServerConnection sc) throws SQLNonTransientException {

MySqlStatementParser parser = new MySqlStatementParser(origSQL);

List statements = parser.parseStatementList(); // @1

if(statements == null || statements.isEmpty() ) {

throw new SQLNonTransientException(String.format(“无效的SQL语句:%s”, origSQL));

}

boolean erFlag = false; //是否是er表

for(SQLStatement stmt : statements ) { // @2

MySqlInsertStatement insertStmt = (MySqlInsertStatement) stmt; // @3

String tableName = insertStmt.getTableName().getSimpleName().toUpperCase();

final TableConfig tc = schema.getTables().get(tableName);

if (null != tc && tc.isChildTable()) { // @4

erFlag = true;

String sql = insertStmt.toString();

final RouteResultset rrs = new RouteResultset(sql, ServerParse.INSERT);

String joinKey = tc.getJoinKey();

//因为是Insert语句,用MySqlInsertStatement进行parse

// MySqlInsertStatement insertStmt = (MySqlInsertStatement) (new MySqlStatementParser(origSQL)).parseInsert();

//判断条件完整性,取得解析后语句列中的joinkey列的index

int joinKeyIndex = getJoinKeyIndex(insertStmt.getColumns(), joinKey); // @5

if (joinKeyIndex == -1) {

String inf = “joinKey not provided :” + tc.getJoinKey() + “,” + insertStmt;

LOGGER.warn(inf);

throw new SQLNonTransientException(inf);

}

//子表不支持批量插入

if (isMultiInsert(insertStmt)) {

String msg = “ChildTable multi insert not provided”;

LOGGER.warn(msg);

throw new SQLNonTransientException(msg);

}

//取得joinkey的值

String joinKeyVal = insertStmt.getValues().getValues().get(joinKeyIndex).toString();

//解决bug #938,当关联字段的值为char类型时,去掉前后"'"

String realVal = joinKeyVal;

if (joinKeyVal.startsWith(“'”) && joinKeyVal.endsWith(“'”) && joinKeyVal.length() > 2) {

realVal = joinKeyVal.substring(1, joinKeyVal.length() - 1);

}

// try to route by ER parent partion key

//如果是二级子表(父表不再有父表),并且分片字段正好是joinkey字段,调用routeByERParentKey

RouteResultset theRrs = RouterUtil.routeByERParentKey(sc, schema, ServerParse.INSERT, sql, rrs, tc, realVal);

if (theRrs != null) {

boolean processedInsert=false;

//判断是否需要全局序列号

if ( sc!=null && tc.isAutoIncrement()) {

String primaryKey = tc.getPrimaryKey();

processedInsert=processInsert(sc,schema,ServerParse.INSERT,sql,tc.getName(),primaryKey);

}

if(processedInsert==false){

rrs.setFinishedRoute(true);

sc.getSession2().execute(rrs, ServerParse.INSERT); // @6

}

// return true;

//继续处理下一条

continue;

}

// route by sql query root parent’s datanode

//如果不是二级子表或者分片字段不是joinKey字段结果为空,则启动异步线程去后台分片查询出datanode

//只要查询出上一级表的parentkey字段的对应值在哪个分片即可, 使用parentKey 去异步查询,然后进行分片

final String findRootTBSql = tc.getLocateRTableKeySql().toLowerCase() + joinKeyVal;

if (LOGGER.isDebugEnabled()) {

LOGGER.debug("find root parent’s node sql " + findRootTBSql);

}

ListenableFuture listenableFuture = MycatServer.getInstance().

getListeningExecutorService().submit(new Callable() {

@Override

public String call() throws Exception {

FetchStoreNodeOfChildTableHandler fetchHandler = new FetchStoreNodeOfChildTableHandler();

// return fetchHandler.execute(schema.getName(), findRootTBSql, tc.getRootParent().getDataNodes());

return fetchHandler.execute(schema.getName(), findRootTBSql, tc.getRootParent().getDataNodes(), sc);

}

});

Futures.addCallback(listenableFuture, new FutureCallback() {

@Override

public void onSuccess(String result) {

//结果为空,证明上一级表中不存在那条记录,失败

if (Strings.isNullOrEmpty(result)) {

StringBuilder s = new StringBuilder();

LOGGER.warn(s.append(sc.getSession2()).append(origSQL).toString() +

" err:" + “can’t find (root) parent sharding node for sql:” + origSQL);

if(!sc.isAutocommit()) { // 处于事务下失败, 必须回滚

sc.setTxInterrupt(“can’t find (root) parent sharding node for sql:” + origSQL);

}

sc.writeErrMessage(ErrorCode.ER_PARSE_ERROR, “can’t find (root) parent sharding node for sql:” + origSQL);

return;

}

if (LOGGER.isDebugEnabled()) {

LOGGER.debug(“found partion node for child table to insert " + result + " sql :” + origSQL);

}

//找到分片,进行插入(和其他的一样,需要判断是否需要全局自增ID)

boolean processedInsert=false;

if ( sc!=null && tc.isAutoIncrement()) {

try {

String primaryKey = tc.getPrimaryKey();

processedInsert=processInsert(sc,schema,ServerParse.INSERT,origSQL,tc.getName(),primaryKey);

} catch (SQLNonTransientException e) {

LOGGER.warn(“sequence processInsert error,”,e);

sc.writeErrMessage(ErrorCode.ER_PARSE_ERROR , “sequence processInsert error,” + e.getMessage());

}

}

if(processedInsert==false){

RouteResultset executeRrs = RouterUtil.routeToSingleNode(rrs, result, origSQL);

sc.getSession2().execute(executeRrs, ServerParse.INSERT);

}

}

@Override

public void onFailure(Throwable t) {

StringBuilder s = new StringBuilder();

LOGGER.warn(s.append(sc.getSession2()).append(origSQL).toString() +

" err:" + t.getMessage());

sc.writeErrMessage(ErrorCode.ER_PARSE_ERROR, t.getMessage() + " " + s.toString());

}

}, MycatServer.getInstance().

getListeningExecutorService());

} else if(erFlag) {

throw new SQLNonTransientException(String.format(“%s包含不是ER分片的表”, origSQL));

}

}

return erFlag;

}

代码@1,利用druid引擎解析SQL,支持多multi执行。这里是解决该问题的关键。

代码@2,一条语句一条语句的执行。

代码@3,能进入到这个方法,说明肯定是insert语句。

代码@4,判断是否是ER字表。

代码@5,主要利用joinKey进行路由计算。

代码@6,直接在分片上依次执行该insert语句。

修复好之后,怎么验证是正确的,先理论,然后再测试。

整个语句的执行流程如下:(参考博文:http://blog.csdn.net/prestigeding/article/details/71247101

set autocommit=0;

insert into ;insert into 语句

commit;语句

这里中间步骤的 inisert into 语句是一个一个执行的,分发到的节点,commit语句会同样下发到相同的节点去执行吗?这里是问题的关键。mycat是这样处理这个问题的,每一个前端连接(ServerConnection)会有持有一个查询Hander(QueryHandler),每一个QueryHanlder里面会持有一个NonBlockingSession,在一次完整的命令处理过程中,NonBlockingSession会保存每个路由节点(执行过的分片节点)与后端连接的BackConnection的对应关系(ConcurrentHashMap<RouteResultsetNode,BackendCone

ction> target)。

/**

* @return previous bound connection

*/

  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值