前言 :
因为新增需求,需要增加分库插入数据的功能。由于原先使用了mycat进行分库查询,所以优先考虑使用mycat进行相应的改造。
同时,该文档仅在个人系统架构前提下进行的分析考虑,因此对于不同架构或情况的小伙伴来说仅供参考。欢迎一起探讨更多方案。
MYCAT分片规则配置说明:
在查看mycat分片(数据片,独立的物理数据库节点)规则时,发现只需要改动少数配置就能支持通过mycat进行分片插入,具体改动文件及内容如下:
rule.xml
在rule.xml增加对应的分片规则和引用函数,若函数需要自定义,则需要在Mycat-Server-1.6.5\src\main\java\io\mycat\route\function的路径下增加新类用于引用,具体实现自行参照mycat其它相关类。
之后在schema.xml的table标签中增加刚刚新增的rule的引用即可,具体可参照mycat的说明文档。
<table name="travelrecord" dataNode="multipleDn$0-99,multipleDn2$100-199" rule="auto-shardinglong" ></table>
同时写sql时需要注意的是必须有分片的关键字段,如上图的id,否则会报错。若insert into A values(),或者insert into A(test_column) values()。
主要问题:
现在遇到这样一个问题:
mycat的确支持按照相应字段进行分片操作,但这个相应字段只允许为单个字段(具体相关源码见底部),但是在业务上又需要支持按照2个字段进行分片操作。那该如何通过mycat实现这一需求?
思路历程:
思路一:
当然,遇到这种问题的话,有时间有精力当然是优先考虑改造源码,但是由于个人时间精力有限,且对mycat的源码了解程度仅限于自己曾经接触使用的部分,所以这一方案只能留到最后。
思路二:
再次结合自身系统来思考时发现,虽然是需要根据字段A和字段B来进行数据分片,但是字段A存于session中,单次登录后该字段的值会保持不变,因此可以考虑在操作mycat之前先对字段A进行相应处理。
同时,好在自身系统使用了多数据源,所以这个问题相对来说就更方便解决了。
因此考虑在对字段A在代码层进行分流,即字段A值为1时,使用数据源①,字段值为2时,使用数据源②。然后数据源①和②配置不同的mycat scheme或服务,再在相应的table上对字段B进行相应的分片规则处理。
这种方案缺点很多。首先,在字段A增加不同值时,需要增加新的数据源配置(当然也可以考虑用通配的方式去匹配,理论上来说应该是可以实现的),同时mycat也要增加相应的配置。且前提要求较为严格,对于超过2个字段或同一sql操作内两个字段的值均有不同时,该方案就不适用。因此需要再考虑其他方案(如果后续还有其他思路)。
思路三:
若需要进行相应分片的表允许改动,或系统刚起步允许改动表结构和更新历史数据的话,可以考虑以下做法。
若需要进行分片判断的表字段为A,B,C,D多个字段的话 ,可以考虑增加字段E,其字段值为CONCAT(A,B,C,D)(oracle:A||B||C||D),然后分片规则定义在该字段上。
这种方案较为简单,且可操作性高,但是对于表结构调整较大,或者sql改动较大的项目,请谨慎!
附录:
批量插入时,mycat的分片处理代码
Mycat-Server-1.6.5\src\main\java\io\mycat\route\parser\druid\impl\DruidInsertParser.java
/**
* insert into .... select .... 或insert into table() values (),(),....
* @param schema
* @param rrs
* @param insertStmt
* @throws SQLNonTransientException
*/
private void parserBatchInsert(SchemaConfig schema, RouteResultset rrs, String partitionColumn,
String tableName, MySqlInsertStatement insertStmt) throws SQLNonTransientException {
//insert into table() values (),(),....
if(insertStmt.getValuesList().size() > 1) {
//字段列数
int columnNum = insertStmt.getColumns().size();
int shardingColIndex = getShardingColIndex(insertStmt, partitionColumn);
if(shardingColIndex == -1) {
String msg = "bad insert sql (sharding column:"+ partitionColumn + " not provided," + insertStmt;
LOGGER.warn(msg);
throw new SQLNonTransientException(msg);
} else {
List<ValuesClause> valueClauseList = insertStmt.getValuesList();
Map<Integer,List<ValuesClause>> nodeValuesMap = new HashMap<Integer,List<ValuesClause>>();
Map<Integer,Integer> slotsMap = new HashMap<>();
TableConfig tableConfig = schema.getTables().get(tableName);
AbstractPartitionAlgorithm algorithm = tableConfig.getRule().getRuleAlgorithm();
for(ValuesClause valueClause : valueClauseList) {
if(valueClause.getValues().size() != columnNum) {
String msg = "bad insert sql columnSize != valueSize:"
+ columnNum + " != " + valueClause.getValues().size()
+ "values:" + valueClause;
LOGGER.warn(msg);
throw new SQLNonTransientException(msg);
}
SQLExpr expr = valueClause.getValues().get(shardingColIndex);
String shardingValue = null;
if(expr instanceof SQLIntegerExpr) {
SQLIntegerExpr intExpr = (SQLIntegerExpr)expr;
shardingValue = intExpr.getNumber() + "";
} else if (expr instanceof SQLCharExpr) {
SQLCharExpr charExpr = (SQLCharExpr)expr;
shardingValue = charExpr.getText();
}
Integer nodeIndex = algorithm.calculate(shardingValue);
if(algorithm instanceof SlotFunction){
slotsMap.put(nodeIndex,((SlotFunction) algorithm).slotValue()) ;
}
//没找到插入的分片
if(nodeIndex == null) {
String msg = "can't find any valid datanode :" + tableName
+ " -> " + partitionColumn + " -> " + shardingValue;
LOGGER.warn(msg);
throw new SQLNonTransientException(msg);
}
if(nodeValuesMap.get(nodeIndex) == null) {
nodeValuesMap.put(nodeIndex, new ArrayList<ValuesClause>());
}
nodeValuesMap.get(nodeIndex).add(valueClause);
}
RouteResultsetNode[] nodes = new RouteResultsetNode[nodeValuesMap.size()];
int count = 0;
for(Map.Entry<Integer,List<ValuesClause>> node : nodeValuesMap.entrySet()) {
Integer nodeIndex = node.getKey();
List<ValuesClause> valuesList = node.getValue();
insertStmt.setValuesList(valuesList);
nodes[count] = new RouteResultsetNode(tableConfig.getDataNodes().get(nodeIndex),
rrs.getSqlType(),insertStmt.toString());
if(algorithm instanceof SlotFunction) {
nodes[count].setSlot(slotsMap.get(nodeIndex));
nodes[count].setStatement(ParseUtil.changeInsertAddSlot(nodes[count].getStatement(),nodes[count].getSlot()));
}
nodes[count++].setSource(rrs);
}
rrs.setNodes(nodes);
rrs.setFinishedRoute(true);
}
} else if(insertStmt.getQuery() != null) { // insert into .... select ....
String msg = "TODO:insert into .... select .... not supported!";
LOGGER.warn(msg);
throw new SQLNonTransientException(msg);
}
}