Apache ShardingSphere HINT 实用指南
作者:陈出新,SphereEx 中间件研发工程师,Apache ShardingSphere Committer,目前专注于 Apache ShardingSphere 内核模块的研发工作。
背景
Apache ShardingSphere 基于用户的实际使用场景,为用户打造了多种实用功能,包括数据分片、读写分离等。在数据分片功能中,Apache ShardingSphere 提供了标准分片、复合分片等多种实用的分片策略,在各种分片策略中,用户又可以配置相关分片算法,从而解决数据分片的问题。在读写分离功能中,ShardingSphere 为用户提供了静态和动态的两种读写分离类型以及丰富的负载均衡算法以满足用户实际需求。
可以看到 ShardingSphere 的分片和读写分离功能已经非常丰富,不过用户的真实使用场景是千变万化的。以多租户场景为例,用户期望按照登录账号所属租户进行分片,但是租户信息却并不是存在于每条业务 SQL 中,这时从 SQL 中提取分片字段的算法将无法发挥作用。再以读写分离为例,大部分场景下用户都希望能够将查询操作路由到从库上执行,但是在某些实时性要求很高的场景下,用户希望将 SQL 强制路由到主库执行,这时读写分离就无法满足业务要求。
基于以上痛点 Apache ShardingSphere 为用户提供了 Hint 功能,用户可以结合实际业务场景,利用 SQL 外部的逻辑进行强制路由或者分片。目前 ShardingSphere 为用户提供了两种 Hint 方式,一种通过 Java API 手动编程,利用 HintManager 进行强制路由和分片,这种方式对采用 JDBC 编程的应用非常友好,只需要少量的代码编写,就能够轻松实现不依赖 SQL 的分片或者强制路由功能。另外一种方式对于不懂开发的 DBA 而言更加友好,ShardingSphere 基于分布式 SQL 提供的使用方式,利用 SQL HINT 和 DistSQL HINT,为用户提供了无需编码就能实现的分片和强制路由功能。接下来,让我们一起了解下这两种使用方式。
基于 HintManager 的手动编程
ShardingSphere 主要通过 HintManager 对象来实现强制路由和分片的功能。利用 HintManager,用户的分片将不用再依赖 SQL。它可以极大地扩展用户的使用场景,让用户可以更加灵活地进行数据分片或者强制路由。目前通过 HintManager,用户可以配合 ShardingSphere 内置的或者自定义的 Hint 算法实现分片功能,还可以通过设置指定数据源或者强制主库读写,实现强制路由功能。在学习 HintManager 的使用之前,让我们先来简单地了解一下它的实现原理,这有助于我们更好地使用它。
HintManager 实现原理
其实通过查看 HintManager 代码,我们可以快速地了解它的原理。
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class HintManager implements AutoCloseable {
private static final ThreadLocal<HintManager> HINT_MANAGER_HOLDER = new ThreadLocal<>();
}
正如你所看到的,ShardingSphere 通过 ThreadLocal 来实现 HintManager 的功能,只要在同一个线程中,用户的分片设置都会得以保留。因此,只要用户在执行 SQL 之前调用 HintManager 相关功能,ShardingSphere 就能在当前线程中获取用户设置的分片或强制路由条件,从而进行分片或者路由操作。了解了 HintManager 的原理之后,让我们一起来学习一下它的使用。
HintManager 的使用
使用 Hint 分片
Hint 分片算法需要用户实现 org.apache.shardingsphere.sharding.api.sharding.hint.HintShardingAlgorithm接口。Apache ShardingSphere 在进行路由时,将会从 HintManager 中获取分片值进行路由操作。
参考配置如下:
rules:
- !SHARDING
tables:
t_order:
actualDataNodes: demo_ds_${0..1}.t_order_${0..1}
databaseStrategy:
hint:
algorithmClassName: xxx.xxx.xxx.HintXXXAlgorithm
tableStrategy:
hint:
algorithmClassName: xxx.xxx.xxx.HintXXXAlgorithm
defaultTableStrategy:
none:
defaultKeyGenerateStrategy:
type: SNOWFLAKE
column: order_id
props:
sql-show: true
获取 HintManager 实例
HintManager hintManager = HintManager.getInstance();
添加分片键
-
使用
hintManager.addDatabaseShardingValue来添加数据源分片键值。 -
使用
hintManager.addTableShardingValue来添加表分片键值。
注:分库不分表情况下,强制路由至某一个分库时,可使用 hintManager.setDatabaseShardingValue 方式添加分片。
清除分片键值
分片键值保存在 ThreadLocal 中,所以需要在操作结束时调用 hintManager.close() 来清除 ThreadLocal 中的内容。
完整代码示例
String sql = "SELECT * FROM t_order";
try (HintManager hintManager = HintManager.getInstance();
Connection conn = dataSource.getConnection();
PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
hintManager.addDatabaseShardingValue("t_order", 1);
hintManager.addTableShardingValue("t_order", 2);
try (ResultSet rs = preparedStatement.executeQuery()) {
while (rs.next()) {
// ...
}
}
}
String sql = "SELECT * FROM t_order";
try (HintManager hintManager = HintManager.getInstance();
Connection conn = dataSource.getConnection();
PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
hintManager.setDatabaseShardingValue(3);
try (ResultSet rs = preparedStatement.executeQuery()) {
while (rs.next()) {
// ...
}
}
}
使用 Hint 强制主库路由
获取 HintManager
与基于 Hint 的数据分片相同。
设置主库路由
使用 hintManager.setWriteRouteOnly 设置主库路由。
清除分片键值
与基于 Hint 的数据分片相同。
完整代码示例
String sql = "SELECT * FROM t_order";
try (HintManager hintManager = HintManager.getInstance();
Connection conn = dataSource.getConnection();
PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
hintManager.setWriteRouteOnly();
try (ResultSet rs = preparedStatement.executeQuery()) {
while (rs.next()) {
// ...
}
}
}
使用 Hint 路由至指定数据库
获取 HintManager
与基于 Hint 的数据分片相同。
设置路由至指定数据库
使用 hintManager.setWriteRouteOnly 设置数据库名称。
完整代码示例
String sql = "SELECT * FROM t_order";
try (HintManager hintManager = HintManager.getInstance();
Connection conn = dataSource.getConnection();
PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
hintManager.setDataSourceName("ds_0");
try (ResultSet rs = preparedStatement.executeQuery()) {
while (rs.next()) {
// ...
}
}
}
清除强制路由值
与基于 Hint 的数据分片相同。
在了解了基于 HintManager 的手动编程方式之后,让我们一起来了解 ShardingSphere 基于分布式 SQL 提供的另一种 Hint 的解决方案。
基于分布式 SQL 的 Hint
Apache ShardingSphere 的分布式 SQL HINT 主要由两种功能组成,一种叫做 SQL HINT,即基于 SQL 注释的方式提供的功能,另外一种是通过 DistSQL 实现的作用于 HintManager 的功能。
SQL HINT
SQL HINT 就是通过在 SQL 语句上增加注释,从而实现强制路由的一种 Hint 方式。它降低了用户改造代码的成本,同时完全脱离了 Java API 的限制,不仅可以在 ShardingSphere-JDBC 中使用,也可以直接在 ShardingSphere-Proxy 上使用。
以下面 SQL 为例,即使用户配置了针对 torder 的相关分片算法,该 SQL 也会直接在数据库 ds0 上原封不动地执行,并返回执行结果。
/* ShardingSphere hint: dataSourceName=ds_0 */
SELECT * FROM t_order;
通过注释的方式我们可以方便地将 SQL 直接送达指定数据库执行而无视其它分片逻辑。以多租户场景为例,用户不用再配置复杂的分库逻辑,也无需改造业务逻辑,只需要将指定库添加到注释信息中即可。在了解了 SQL HINT 的基本使用之后,让我们一起来了解一下 SQL HINT 的实现原理。
SQL HINT 的实现原理
其实了解 Apache ShardingSphere 的读者朋友们一定对 SQL 解析引擎不会感到陌生。SQL HINT 实现的第一步就是提取 SQL 中的注释信息。利用 antlr4 的通道功能,可以将 SQL 中的注释信息单独送至特定的隐藏通道,ShardingSphere 也正是利用该功能,在生成解析结果的同时,将隐藏通道中的注释信息一并提取出来了。具体实现如下方代码所示。
- 将 SQL 中的注释送入隐藏通道:
lexer grammar Comments;
import Symbol;
BLOCK_COMMENT: '/*' .*? '*/' -> channel(HIDDEN);
INLINE_COMMENT: (('-- ' | '#') ~[\r\n]* ('\r'? '\n' | EOF) | '--' ('\r'? '\n' | EOF)) -> channel(HIDDEN);
- 访问语法树后增加对于注释信息的提取:
public <T> T visit(final ParseContext parseContext) {
ParseTreeVisitor<T> visitor = SQLVisitorFactory.newInstance(databaseType, visitorType, SQLVisitorRule.valueOf(parseContext.getParseTree().getClass()), props);
T result = parseContext.getParseTree().accept(visitor);
appendSQLComments(parseContext, result);
return result;
}
private <T> void appendSQLComments(final ParseContext parseContext, final T visitResult) {
if (!parseContext.getHiddenTokens().isEmpty() && visitResult instanceof AbstractSQLStatement) {
Collection<CommentSegment> commentSegments = parseContext.getHiddenTokens().stream().map(each -> new CommentSegment(each.getText(), each.getStartIndex(), each.getStopIndex()))
.collect(Collectors.toList());
((AbstractSQLStatement) visitResult).getCommentSegments().addAll(commentSegments);
}
}
提取出用户 SQL 中的注释信息之后,我们就需要根据注释信息来进行相关强制路由了。既然是路由,那么自然就需要使用 Apache ShardingSphere 的路由引擎,我们在路由引擎上做了一些针对 HINT 的改造。
public RouteContext route(final LogicSQL logicSQL, final ShardingSphereMetaData metaData) {
RouteContext result = new RouteContext();
Optional<String> dataSourceName = findDataSourceByHint(logicSQL.getSqlStatementContext(), metaData.getResource().getDataSources());
if (dataSourceName.isPresent()) {
result.getRouteUnits().add(new RouteUnit(new RouteMapper(dataSourceName.get(), dataSourceName.get()), Collections.emptyList()));
return result;
}
for (Entry<ShardingSphereRule, SQLRouter> entry : routers.entrySet()) {
if (result.getRouteUnits().isEmpty()) {
result = entry.getValue().createRouteContext(logicSQL, metaData, entry.getKey(), props);
} else {
entry.getValue().decorateRouteContext(result, logicSQL, metaData, entry.getKey(), props);
}
}
if (result.getRouteUnits().isEmpty() && 1 == metaData.getResource().getDataSources().size()) {
String singleDataSourceName = metaData.getResource().getDataSources().keySet().iterator().next();
result.getRouteUnits().add(new RouteUnit(new RouteMapper(singleDataSourceName, singleDataSourceName), Collections.emptyList()));
}
return result;
}
ShardingSphere 首先发现了符合定义的 SQL 注释,再经过基本的校验之后,就会直接返回用户指定的路由结果,从而实现强制路由功能。在了解了 SQL HINT 的基本原理之后,让我们一起学习如何使用 SQL HINT。
如何使用 SQL HINT
SQL HINT 的使用非常简单,无论是 ShardingSphere-JDBC 还是 ShardingSphere-Porxy,都可以使用。
第一步打开注释解析开关。将 sqlCommentParseEnabled 设置为 true。
第二步在 SQL 上增加注释即可。目前 SQL HINT 支持指定数据源路由和主库路由。
-
指定数据源路由:目前只支持路由至一个数据源。注释格式暂时只支持
/* */,内容需要以ShardingSphere hint:开始,属性名为dataSourceName。 -
/* ShardingSphere hint: dataSourceName=ds_0 */ SELECT * FROM t_order; -
主库路由:注释格式暂时只支持
/* */,内容需要以ShardingSphere hint:开始,属性名为writeRouteOnly。
/* ShardingSphere hint: writeRouteOnly=true */
SELECT * FROM t_order;
DistSQL HINT
Apache ShardingSphere 的 DistSQL 也提供了 Hint 相关功能,让用户可以通过 ShardingSphere-Proxy 来实现分片和强制路由功能。
DistSQL HINT 的实现原理
同前文一致,在学习使用 DistSQL HINT 功能之前,让我们一起来了解一下 DistSQL Hint 的实现原理。DistSQL HINT 的实现原理非常简单,其实就是通过操作 HintManager 实现的 HINT 功能。以读写分离 Hint 为例,当用户通过 ShardingSphere-Proxy 执行以下 SQL 时,其实 ShardingSphere 内部对 SQL 做了如下方代码所示的操作。
-- 强制主库读写
set readwrite_splitting hint source = write
@RequiredArgsConstructor
public final class SetReadwriteSplittingHintExecutor extends AbstractHintUpdateExecutor<SetReadwriteSplittingHintStatement> {
private final SetReadwriteSplittingHintStatement sqlStatement;
@Override
public ResponseHeader execute() {
HintSourceType sourceType = HintSourceType.typeOf(sqlStatement.getSource());
switch (sourceType) {
case AUTO:
HintManagerHolder.get().setReadwriteSplittingAuto();
break;
case WRITE:
HintManagerHolder.get().setWriteRouteOnly();
break;
default:
break;
}
return new UpdateResponseHeader(new EmptyStatement());
}
}
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class HintManagerHolder {
private static final ThreadLocal<HintManager> HINT_MANAGER_HOLDER = new ThreadLocal<>();
/**
* Get an instance for {@code HintManager} from {@code ThreadLocal},if not exist,then create new one.
*
* @return hint manager
*/
public static HintManager get() {
if (HINT_MANAGER_HOLDER.get() == null) {
HINT_MANAGER_HOLDER.set(HintManager.getInstance());
}
return HINT_MANAGER_HOLDER.get();
}
/**
* remove {@code HintManager} from {@code ThreadLocal}.
*/
public static void remove() {
HINT_MANAGER_HOLDER.remove();
}
}
用户执行 SQL 之后,DistSQL 解析引擎会首先识别出该 SQL 是读写分离 Hint 的 SQL,同时会提取出用户想要自动路由或者强制到主库的字段。之后它会采用 SetReadwriteSplittingHintExecutor 执行器去执行 SQL,从而将正确操作设置到 HintManager 中,进而实现强制路由主库的功能。
DistSQL HINT 的使用
下表为大家展示了 DistSQL Hint 的相关语法。

本文详细介绍了 Hint 使用的两种方式以及基本原理,相信通过本文,读者朋友们对 Hint 都有了一些基本了解了,大家可以根据自己的需求来选择使用合适的方式。如果在使用过程中遇到任何问题,或者有任何建议想法,都欢迎来社区反馈。
GitHub:https://github.com/apache/shardingsphere
中文社区:https://community.sphere-ex.com/
欢迎添加社区经理微信(ss_assistant_1)加入交流群,与众多 ShardingSphere 爱好者一同交流。
相关推荐
深度解析ShardingJDBC:Java开发者的分库分表利器
ShardingJDBC是一个轻量级的Java框架,专为处理分库分表场景而设计。起源于当当网,后经由多家大型互联网企业的验证与发展,它已成为Apache软件基金会的顶级项目。ShardingJDBC通过数据分片和读写分离,使Java应用能透明地访问分库分表的多个数据源。它在JDBC层提供服务,无需额外部署,兼容各种ORM框架。与ShardingProxy不同,ShardingJDBC在客户端执行分库分表逻辑,提供灵活的配置但需要业务方自定义逻辑。 ShardingJDBC实战表明,其核心在于配置分片算法,常见的包括inline、standard、complex和hint分片策略。在具体实施中,它支持定义逻辑表、真实表、数据节点、绑定表和广播表等概念,实现复杂的分片和分布策略。通过配置application.properties文件,开发者可以指定数据源、表的分布、分片策略及其他参数。
ShardingJDBC分库分表mysql数据库实战小滴新版
本课程是新一代分库分表 Sharding-JDBC 最佳实践专题课程。课程内容丰富,涵盖了 MySQL 架构演变升级、分库分表的优缺点、常见策略及中间件介绍等多个方面。 课程首先介绍了分库分表的背景,包括 MySQL 数据库架构演变历史以及业务增长下数据库性能优化思路。接着深入探讨了分库分表的各种方式,如垂直分表、垂直分库、水平分表和水平分库,并对其进行了总结。还讲解了 MySQL 数据库水平分库分表的常见策略,如 range、hash 取模等。 课程对常见的分库分表中间件进行了介绍,重点讲解了 Apache ShardingSphere 和 Sharding-Jdbc 的相关知识,包括常见概念术语和分片算法。在实战部分,通过新版 Spring Boot 2.5 + MybatisPlus 整合 Sharding-Jdbc,进行项目创建、数据源配置、单元测试等操作。同时,课程还涉及分库分表进阶内容,如 Snowflake 雪花算法实战、广播表和绑定表配置实战以及 Sharding-Jdbc 多种分片策略实战,包括精准分片算法、范围分片算法、复合分片算法和 Hint 分片算法等。此外,课
MySQL更新字段值相等时的执行机制[项目代码]
本文探讨了MySQL在更新数据时,若被更新字段的新值与旧值相等,是否会真正执行SQL的问题。作者提出了三种可能的猜想,并通过实际测试验证了MySQL会按正常流程执行更新操作,即使值未变化。文章详细描述了测试过程,包括事务隔离性、InnoDB引擎的行为以及MySQL的优化机制。最后指出,虽然这种优化对日常开发帮助不大,但理解其原理有助于更深入地掌握MySQL的执行流程。
基于混合储能协调控制的三相并网逆变系统设计【附说明文档】
内容概要:本文围绕“基于混合储能协调控制的三相并网逆变系统设计”展开,系统阐述了蓄电池与飞轮储能等混合储能系统在微电网中的协同控制策略。研究涵盖三相并网逆变器的能量管理、SOC(荷电状态)恒压恒流充放电控制、PQ控制与双闭环控制等核心技术,并基于Matlab/Simulink仿真平台实现了系统建模与动态仿真验证。文中深入探讨了飞轮储能系统的机侧PQ控制与网侧变流器的恒压双闭环控制,分析了微电网在调频调峰、功率协同分配等方面的运行特性,强调多能源间的协调优化与系统的快速动态响应能力,旨在提升微电网的运行稳定性、电能质量及能源利用效率。; 适合人群:具备电力电子、自动化或电气工程等相关专业背景,从事新能源发电、微电网控制、储能系统集成与优化等领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①深入研究混合储能系统在微电网中的协调控制机制与能量管理策略;②掌握三相并网逆变器的建模、控制算法设计及Matlab/Simulink仿真方法;③实现储能系统的高效充放电管理与并网功率精确控制;④为学术论文撰写、科研课题申报、工程项目开发或实验复现提供完整的技术方案与参考实例。; 阅读建议:建议结合所提供的仿真模型、说明文档及参考文献,按照系统架构从单元控制到整体协调的顺序分模块学习,重点关注各控制策略的设计原理、参数整定方法与协同逻辑。推荐在Matlab环境中动手复现仿真,通过调整参数和工况来观察系统响应,以深化对理论知识的理解和工程应用能力的掌握。
科技中介服务机构在服务企业时,如何通过平台降低服务门槛和操作复杂度?.docx
科技中介服务机构在服务企业时,如何通过平台降低服务门槛和操作复杂度?



