一、改写引擎
上一篇文章说了shardingsphereSQL解析的大体源码流程,可以快速帮助新了解的人员。本篇介绍sql改写,实际上如果不使用sharding的分片的话,几乎原sql是什么,sql改写之后依旧是什么。
1.什么是SQL改写
开发人员在面向逻辑库与逻辑表书写的 SQL时,并不能够直接在真实的数据库中执行,SQL 改写用于将逻辑 SQL 改写为在真实数据库中可以正确执行的 SQL。 它包括正确性改写和优化改写两部分。
1.1.正确性改写
在包含分表的场景中,需要将分表配置中的逻辑表名称改写为路由之后所获取的真实表名称。仅分库则不需要表名称的改写。除此之外,还包括补列和分页信息修正等内容。
1.2 标识符改写
需要改写的标识符包括表名称、索引名称以及 Schema 名称。
表名称改写
表名称改写是指将找到逻辑表在原始 SQL 中的位置,并将其改写为真实表的过程。表名称改写是一个典型的需要对 SQL 进行解析的场景。 从一个最简单的例子开始,若逻辑 SQL 为:
SELECT order_id FROM t_order WHERE order_id=1;
假设该 SQL 配置分片键 order_id,并且 order_id=1 的情况,将路由至分片表 1。那么改写之后的 SQL 应该为:
SELECT order_id FROM t_order_1 WHERE order_id=1;
索引名称改写
索引名称是另一个有可能改写的标识符。 在某些数据库中(如 MySQL、SQLServer),索引是以表为维度创建的,在不同的表中的索引是可以重名的; 而在另外的一些数据库中(如 PostgreSQL、Oracle),索引是以数据库为维度创建的,即使是作用在不同表上的索引,它们也要求其名称的唯一性。
在 ShardingSphere 中,管理 Schema 的方式与管理表如出一辙,它采用逻辑 Schema 去管理一组数据源。 因此,ShardingSphere 需要将用户在 SQL 中书写的逻辑 Schema 替换为真实的数据库 Schema。
ShardingSphere 目前还不支持在 DQL 和 DML 语句中使用 Schema。 它目前仅支持在数据库管理语句中使用 Schema,例如:
SHOW COLUMNS FROM t_order FROM order_ds;
Schema 的改写指的是将逻辑 Schema 采用单播路由的方式,改写为随机查找到的一个正确的真实 Schema。
补列改写
需要在查询语句中补列通常由两种情况导致。 第一种情况是 ShardingSphere 需要在结果归并时获取相应数据,但该数据并未能通过查询的 SQL 返回。 这种情况主要是针对 GROUP BY 和 ORDER BY。结果归并时,需要根据 GROUP BY 和 ORDER BY 的字段项进行分组和排序,但如果原始 SQL 的选择项中若并未包含分组项或排序项,则需要对原始 SQL 进行改写。 先看一下原始 SQL 中带有结果归并所需信息的场景:
SELECT order_id, user_id FROM t_order ORDER BY user_id;;
由于使用 user_id 进行排序,在结果归并中需要能够获取到 user_id 的数据,而上面的 SQL 是能够获取到 user_id 数据的,因此无需补列。
如果选择项中不包含结果归并时所需的列,则需要进行补列,如以下 SQL:
SELECT order_id FROM t_order ORDER BY user_id;
由于原始 SQL 中并不包含需要在结果归并中需要获取的 user_id,因此需要对 SQL 进行补列改写。补列之后的 SQL 是:
SELECT order_id, user_id AS ORDER_BY_DERIVED_0 FROM t_order ORDER BY user_id;
补列只会补充缺失的列,不会全部补充,而且,在 SELECT 语句中包含 * 的 SQL,也会根据表的元数据信息选择性补列。
2.源码分析
依旧从核心-命令执行类 CommandExecutorTask.java 开始
private boolean executeCommand(final ChannelHandlerContext context, final PacketPayload payload) throws SQLException {
CommandExecuteEngine commandExecuteEngine = databaseProtocolFrontendEngine.getCommandExecuteEngine();
CommandPacketType type = commandExecuteEngine.getCommandPacketType(payload);
CommandPacket commandPacket = commandExecuteEngine.getCommandPacket(payload, type, connectionSession);
CommandExecutor commandExecutor = commandExecuteEngine.getCommandExecutor(type, commandPacket, connectionSession);
try {
Collection<DatabasePacket<?>> responsePackets = commandExecutor.execute();
if (responsePackets.isEmpty()) {
return false;
}
responsePackets.forEach(context::write);
if (commandExecutor instanceof QueryCommandExecutor) {
commandExecuteEngine.writeQueryData(context, connectionSession.getBackendConnection(), (QueryCommandExecutor) commandExecutor, responsePackets.size());
}
return true;
} catch (final SQLException | ShardingSphereSQLException | SQLDialectException ex) {
databaseProtocolFrontendEngine.handleException(connectionSession, ex);
throw ex;
} finally {
commandExecutor.close();
}
}
上一篇中sql解析完成,会将其封装为CommandExecutor 执行器对象,由于代理的是mysql的数据库,所以创建出来的执行器为MySQLComQueryPacketExecutor,sql改写在commandExecutor.execute();里面
真正执行的是MySQLComQueryPacketExecutor的execute方法
public Collection<DatabasePacket<?>> execute() throws SQLException {//由后端处理程序执行,具体的创建在构造方法上,也就是CommmandExecutorTask再获取执行器的时候执行的,然后真正执行的是 DatabaseCommunicationEngine
ResponseHeader responseHeader = proxyBackendHandler.execute();
if (responseHeader instanceof QueryResponseHeader) {
return processQuery((QueryResponseHeader) responseHeader);
}
responseType = ResponseType.UPDATE;
return processUpdate((UpdateResponseHeader) responseHeader);
}
DatabaseCommunicationEngine.execute()
里面真正进行sql重写的就是new KernelProcessor().generateExecutionContext()
MetaDataContexts metaDataContexts = ProxyContext.getInstance().getContextManager().getMetaDataContexts();
SQLFederationDeciderContext deciderContext = decide(queryContext, metaDataContexts.getMetaData().getProps(), database)
// 一般来说 select 这种不会走,,查询系统表结构会走,也就是系统表会进行处理;
if (deciderContext.isUseSQLFederation()) {
prepareFederationExecutor();
ResultSet resultSet = doExecuteFederation(queryContext, metaDataContexts);
return processExecuteFederation(resultSet, metaDataContexts);
}//里面包含sql重写
ExecutionContext executionContext = new KernelProcessor().generateExecutionContext(queryContext, database, metaDataContexts.getMetaData().getGlobalRuleMetaData(),
metaDataContexts.getMetaData().getProps(), backendConnection.getConnectionSession().getConnectionContext());
if (executionContext.getExecutionUnits().isEmpty()) {
return new UpdateResponseHeader(executionContext.getSqlStatementContext().getSqlStatement());
}
proxySQLExecutor.checkExecutePrerequisites(executionContext);
// 准备执行语句, 返回执行结果
List result = proxySQLExecutor.execute(executionContext);
refreshMetaData(executionContext);
Object executeResultSample = result.iterator().next();
return executeResultSample instanceof QueryResult
? processExecuteQuery(executionContext, result, (QueryResult) executeResultSample)
: processExecuteUpdate(executionContext, result);
}
KernelProcessor.generateExecutionContext()
其实里面这个rewrite,重写的条件呢,就是在上面的两种情况,使用情况比较多的就是。
1.用到了分片 逻辑sql改写、还有一种就是补列的情况。会根据配置文件中写入的rule进行匹配。
2. 就是* 号中包含了加密列
public ExecutionContext generateExecutionContext(final QueryContext queryContext, final ShardingSphereDatabase database, final ShardingSphereRuleMetaData globalRuleMetaData,
final ConfigurationProperties props, final ConnectionContext connectionContext) {
RouteContext routeContext = route(queryContext, database, props, connectionContext);// 加密规则上下文
// 改写完成的sql包含在这里面
SQLRewriteResult rewriteResult = rewrite(queryContext, database, globalRuleMetaData, props, routeContext, connectionContext);
// 执行上下文 查询上下文
ExecutionContext result = createExecutionContext(queryContext, database, routeContext, rewriteResult);
logSQL(queryContext, props, result);
return result;
}
二、总结
本篇主要介绍了,shardingsphere SQL改写,中间经过了什么流程,sql改写在什么情况下发生,实际上
select * from tttttt1_copy1这个sql如果在不分片的情况下,是不会发生重写的,并不是将* 改为具体的字段。