Sharding-JDBC 系列
- 第一篇 Sharding-JDBC 源码之启动流程分析
- 第二篇 Sharding-JDBC 源码之 SQL 解析
- 第三篇 Sharding-JDBC 源码之 SQL 路由
- 第四篇 Sharding-JDBC 源码之 SQL 改写(本文)
- 第五篇 Sharding-JDBC 源码之 SQL 执行
- 第六篇 Sharding-JDBC 源码之结果集归并
在完成 SQL 路由后,我们就会拿到实际要查询的数据源名称及实际表节点。这时候就需要对原逻辑 SQL 进行改写,将逻辑表名替换为真实表名,这一步 Sharding-JDBC 是由改写引擎
SQLRewriteEngine
来完成的。
目录
在看具体源码之前,先来了解下 SQL 改写有哪些类型
改写类型
正确性改写
标识符改写
需要改写的标识符包括表名称、索引名称以及Schema名称。如逻辑 SQL 为:
select goods_name from t_goods where goods_id = 1;
当 goods_id = 1时,将会路由到 分片表 1,那么改写后的 SQL 为:
select goods_name from t_goods_1 where goods_id = 1;
补列
补列通常由三种情况导致:
- 需要在结果归并时获取数据,但该列数据并未从查询的 SQL 中返回,这种场景主要针对GROUP BY和ORDER BY。结果归并时需要根据 GROUP BY和ORDER BY的字段进行分组和排序,但如果原始SQL的选择项中若并未包含分组项或排序项,则需要对原始SQL进行改写。如逻辑 SQL 为:
select goods_name from t_goods order by goods_id;
由于原 SQL 中不包含归并结果时需要的 goods_id 列,因此需要改写补列,改写后的 SQL 为:
select goods_name, goods_id AS ORDER_BY_DERIVED_0 from t_goods order by goods_id;
补列只会补充缺失的列,不会全部补充。
- 在使用 AVG 聚集函数时,在分布式的场景中,使用avg1 + avg2 + avg3 / 3计算平均值并不正确,需要改写为 (sum1 + sum2 + sum3) / (count1 + count2 + count3)。 这就需要将包含AVG的SQL改写为SUM和COUNT,并在结果归并时重新计算平均值。如以下SQL:
SELECT AVG(price) FROM t_order WHERE user_id=1;
需要改写为:
SELECT COUNT(price) AS AVG_DERIVED_COUNT_0, SUM(price) AS AVG_DERIVED_SUM_0 FROM t_order WHERE user_id=1;
然后才能够通过结果归并正确的计算平均值。
- 在执行INSERT的SQL语句时,如果使用了Sharding-JDBC分布式自增主键的生成策略,则需要通过补列,让使用方无需改动现有代码,即可将分布式自增主键透明的替换数据库现有的自增主键,如原逻辑 SQL 为:
INSERT INTO t_goods (`goods_name`) VALUES ('番茄鸡蛋盖浇饭');
配置自增主键后,SQL 改写为:
INSERT INTO t_goods (`goods_name`, goods_id) VALUES ('番茄鸡蛋盖浇饭', xxxxx);
改写后的SQL将在INSERT FIELD和INSERT VALUE的最后部分增加主键列名称以及自动生成的自增主键值。上述SQL中的xxxxx表示自动生成的自增主键值。
- 分页修正(略)
- 批量拆分(略)
优化改写
- 单节点优化(略)
- 流式归并优化(略)
由于篇幅原因,其他改写类型本文暂不再介绍,官网介绍的很详细,请移步官网查看:内部剖析-改写引擎
难得的中文官网,大家不要错过。配张官网改写引擎结构图:
源码分析
从源码上,改写的代码主要在 rewrite 包中:
上一章中,拿到 routingResult
后,会初始化 SQL 改写引擎 SQLRewriteEngine
,
public SQLRouteResult route(final String logicSQL, final List<Object> parameters, final SQLStatement sqlStatement) {
// ...... 省略
// 获取路由结果
RoutingResult routingResult = route(parameters, sqlStatement, shardingConditions);
// 1. 初始化改写引擎
SQLRewriteEngine rewriteEngine = new SQLRewriteEngine(shardingRule, logicSQL, databaseType, sqlStatement, shardingConditions, parameters);
// 2. 根据 tableUnits.size 判断是否单路由
boolean isSingleRouting = routingResult.isSingleRouting();
if (sqlStatement instanceof SelectStatement && null != ((SelectStatement) sqlStatement).getLimit()) {
processLimit(parameters, (SelectStatement) sqlStatement, isSingleRouting);
}
// 3. 执行改写,生成 SQLBuilder 对象
SQLBuilder sqlBuilder = rewriteEngine.rewrite(!isSingleRouting);
for (TableUnit each : routingResult.getTableUnits().getTableUnits(