ShardingSphere

概念

数据分片

背景

传统的将数据集中存储至单一数据节点的解决方案,在性能、可用性和运维成本这三方面已经难于满足 互联网的海量数据场景。

从性能方面来说,由于关系型数据库大多采用 B+ 树类型的索引,在数据量超过阈值的情况下,索引深度 的增加也将使得磁盘访问的 IO 次数增加,进而导致查询性能的下降;同时,高并发访问请求也使得集中 式数据库成为系统的最大瓶颈。

从可用性的方面来讲,服务化的无状态型,能够达到较小成本的随意扩容,这必然导致系统的最终压力 都落在数据库之上。而单一的数据节点,或者简单的主从架构,已经越来越难以承担。数据库的可用性, 已成为整个系统的关键。

从运维成本方面考虑,当一个数据库实例中的数据达到阈值以上,对于 DBA 的运维压力就会增大。数据 备份和恢复的时间成本都将随着数据量的大小而愈发不可控。一般来讲,单一数据库实例的数据的阈值 在 1TB 之内,是比较合理的范围。

在传统的关系型数据库无法满足互联网场景需要的情况下,将数据存储至原生支持分布式的 NoSQL 的尝 试越来越多。但 NoSQL 对 SQL 的不兼容性以及生态圈的不完善,使得它们在与关系型数据库的博弈中始 终无法完成致命一击,而关系型数据库的地位却依然不可撼动。

数据分片指按照某个维度将存放在单一数据库中的数据分散地存放至多个数据库或表中以达到提升性能 瓶颈以及可用性的效果。数据分片的有效手段是对关系型数据库进行分库和分表。分库和分表均可以有 效的避免由数据量超过可承受阈值而产生的查询瓶颈。除此之外,分库还能够用于有效的分散对数据库 单点的访问量;分表虽然无法缓解数据库压力,但却能够提供尽量将分布式事务转化为本地事务的可能, 一旦涉及到跨库的更新操作,分布式事务往往会使问题变得复杂。使用多主多从的分片方式,可以有效 的避免数据单点,从而提升数据架构的可用性。

通过分库和分表进行数据的拆分来使得各个表的数据量保持在阈值以下,以及对流量进行疏导应对高访 问量,是应对高并发和海量数据系统的有效手段。数据分片的拆分方式又分为垂直分片和水平分片。

垂直分片

按照业务拆分的方式称为垂直分片,又称为纵向拆分,它的核心理念是专库专用。在拆分之前,一个数 据库由多个数据表构成,每个表对应着不同的业务。而拆分之后,则是按照业务将表进行归类,分布到 不同的数据库中,从而将压力分散至不同的数据库。下图展示了根据业务需要,将用戶表和订单表垂直 分片到不同的数据库的方案。

![
截屏2021-01-19 下午11.05.48.png

]

水平分片

水平分片又称为横向拆分。相对于垂直分片,它不再将数据根据业务逻辑分类,而是通过某个字段(或 某几个字段),根据某种规则将数据分散至多个库或表中,每个分片仅包含数据的一部分。例如:根据主 键分片,偶数主键的记录放入 0 库(或表),奇数主键的记录放入 1 库(或表),如下图所示。

水平分片从理论上突破了单机数据量处理的瓶颈,并且扩展相对自由,是分库分表的标准解决方案

截屏2021-01-19 下午11.06.13.png

挑战

虽然数据分片解决了性能、可用性以及单点备份恢复等问题,但分布式的架构在获得了收益的同时,也

引入了新的问题。

面对如此散乱的分库分表之后的数据,应用开发工程师和数据库管理员对数据库的操作变得异常繁重就 是其中的重要挑战之一。他们需要知道数据需要从哪个具体的数据库的分表中获取。

另一个挑战则是,能够正确的运行在单节点数据库中的 SQL,在分片之后的数据库中并不一定能够正确 运行。例如,分表导致表名称的修改,或者分⻚、排序、聚合分组等操作的不正确处理。

跨库事务也是分布式的数据库集群要面对的棘手事情。合理采用分表,可以在降低单表数据量的情况下, 尽量使用本地事务,善于使用同库不同表可有效避免分布式事务带来的麻烦。在不能避免跨库事务的场 景,有些业务仍然需要保持事务的一致性。而基于 XA 的分布式事务由于在并发度高的场景中性能无法满 足需要,并未被互联网巨头大规模使用,他们大多采用最终一致性的柔性事务代替强一致事务。

核心概念

逻辑表

水平拆分的数据库(表)的相同逻辑和数据结构表的总称。例:订单数据根据主键尾数拆分为 10 张表,分 别是 t_order_0 到 t_order_9,他们的逻辑表名为 t_order。

真实表

在分片的数据库中真实存在的物理表。即上个示例中的 t_order_0 到 t_order_9。

数据节点

数据分片的最小单元。由数据源名称和数据表组成,例:ds_0.t_order_0。

绑定表

指分片规则一致的主表和子表。例如:t_order 表和 t_order_item 表,均按照 order_id 分片,则 此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提 升。举例说明,如果 SQL 为:

SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o. order_id in (10, 11);

在不配置绑定表关系时,假设分片键 order_id 将数值 10 路由至第 0 片,将数值 11 路由至第 1 片,那 么路由后的 SQL 应该为 4 条,它们呈现为笛卡尔积:

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o. order_id in (10, 11);
SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o. order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o. order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o. order_id in (10, 11);

在配置绑定表关系后,路由的 SQL 应该为 2 条:

其中 t_order 在 FROM 的最左侧,ShardingSphere 将会以它作为整个绑定表的主表。所有路由计算将 会只使用主表的策略,那么 t_order_item 表的分片计算将会使用 t_order 的条件。故绑定表之间的 分区键要完全相同。

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o. order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o. order_id in (10, 11);

广播表

指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致。适用于数据量不 大且需要与海量数据的表进行关联查询的场景,例如:字典表。

分片

  • 分片键

用于分片的数据库字段,是将数据库(表)水平拆分的关键字段。例:将订单表中的订单主键的尾数取模 分片,则订单主键为分片字段。SQL 中如果无分片字段,将执行全路由,性能较差。除了对单分片字段的 支持,Apache ShardingSphere 也支持根据多个字段进行分片。

  • 分片算法

    通过分片算法将数据分片,支持通过 =、>=、<=、>、<、BETWEEN 和 IN 分片。分片算法需要应用方开 发者自行实现,可实现的灵活度非常高。

    目前提供 4 种分片算法。由于分片算法和业务实现紧密相关,因此并未提供内置分片算法,而是通过分 片策略将各种场景提炼出来,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。

    • 标准分片算法
      对应 StandardShardingAlgorithm,用于处理使用单一键作为分片键的 =、IN、BETWEEN AND、>、<、>=、

      <= 进行分片的场景。需要配合 StandardShardingStrategy 使用。

    • 复合分片算法

      对应 ComplexKeysShardingAlgorithm,用于处理使用多键作为分片键进行分片的场景,包含多个分片键 的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合 ComplexShardingStrategy 使用。-

  • Hint 分片算法
    对应 HintShardingAlgorithm,用于处理使用 Hint 行分片的场景。需要配合 HintShardingStrategy 使用。

  • 分片策略

    包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。真正可用于分片操作的是分片键 + 分 片算法,也就是分片策略。目前提供 5 种分片策略。

    • 标准分片策略

      对应 StandardShardingStrategy。提供对 SQ L 语句中的 =, >, <, >=, <=, IN 和 BETWEEN AND 的分片操作 支持。StandardShardingStrategy 只支持单分片键,提供 PreciseShardingAlgorithm 和 RangeShardingAl- gorithm 两个分片算法。PreciseShardingAlgorithm 是必选的,用于处理 = 和 IN 的分片。RangeShardingAl- gorithm 是可选的,用于处理 BETWEEN AND, >, <, >=, <= 分片,如果不配置 RangeShardingAlgorithm, SQL 中的 BETWEEN AND 将按照全库路由处理。

    • 复合分片策略

    对应 ComplexShardingStrategy。复合分片策略。提供对 SQL 语句中的 =, >, <, >=, <=, IN 和 BETWEEN AND 的分片操作支持。ComplexShardingStrategy 支持多分片键,由于多分片键之间的关系复杂,因此并 未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现, 提供最大的灵活度。

    • Hint 分片策略
      对应 HintShardingStrategy。通过 Hint 指定分片值而非从 SQL 中提取分片值的方式进行分片的策略。

    • 不分片策略
      对应 NoneShardingStrategy。不分片的策略。

    SQL Hint

    对于分片字段非 SQL 决定,而由其他外置条件决定的场景,可使用 SQL Hint 灵活的注入分片字段。例:

    内部系统,按照员工登录主键分库,而数据库中并无此字段。SQL Hint 支持通过 Java API 和 SQL 注释 (待实现)两种方式使用。

  • 分布式主键

    • 实现动机

    传统数据库软件开发中,主键自动生成技术是基本需求。而各个数据库对于该需求也提供了相应的支持, 比如 MySQL 的自增键,Oracle 的自增序列等。数据分片后,不同数据节点生成全局唯一主键是非常棘手 的问题。同一个逻辑表内的不同实际表之间的自增键由于无法互相感知而产生重复主键。虽然可通过约 束自增主键初始值和步⻓的方式避免碰撞,但需引入额外的运维规则,使解决方案缺乏完整性和可扩展 性。

    目前有许多第三方解决方案可以完美解决这个问题,如 UUID 等依靠特定算法自生成不重复键,或者通过 引入主键生成服务等。为了方便用戶使用、满足不同用戶不同使用场景的需求,Apache ShardingSphere 不仅提供了内置的分布式主键生成器,例如 UUID、SNOWFLAKE,还抽离出分布式主键生成器的接口, 方便用戶自行实现自定义的自增主键生成器。

    • 内置的主键生成器

    UUID

    SNOWFLAKE

  • 强制分片路由

    • 实现动机

    通过解析 SQL 语句提取分片键列与值并进行分片是 Apache ShardingSphere 对 SQL 零侵入的实现方式。 若 SQL 语句中没有分片条件,则无法进行分片,需要全路由。

    在一些应用场景中,分片条件并不存在于 SQL,而存在于外部业务逻辑。因此需要提供一种通过外部指 定分片结果的方式,在 Apache ShardingSphere 中叫做 Hint。

    • 实现机制

    Apache ShardingSphere 使用 ThreadLocal 管理分片键值。可以通过编程的方式向 HintManager 中 添加分片条件,该分片条件仅在当前线程内生效。

    除了通过编程的方式使用强制分片路由,Apache ShardingSphere 还计划通过 SQL 中的特殊注释的方式 引用 Hint,使开发者可以采用更加透明的方式使用该功能。

    指定了强制分片路由的 SQL 将会无视原有的分片逻辑,直接路由至指定的真实数据节点。

内核剖析

ShardingSphere 的 3 个产品的数据分片主要流程是完全一致的。核心由 SQL 解析 => 执行器优化 => SQL 路由 => SQL 改写 => SQL 执行 => 结果归并的流程组成。

SQL 解析

分为词法解析和语法解析。先通过词法解析器将 SQL 拆分为一个个不可再分的单词。再使用语法解析器 对 SQL 进行理解,并最终提炼出解析上下文。解析上下文包括表、选择项、排序项、分组项、聚合函数、 分⻚信息、查询条件以及可能需要修改的占位符的标记。

执行器优化

合并和优化分片条件,如 OR 等。 SQL 路由

根据解析上下文匹配用戶配置的分片策略,并生成路由路径。目前支持分片路由和广播路由。

SQL 改写
将 SQL 改写为在真实数据库中可以正确执行的语句。SQL 改写分为正确性改写和优化改写。

SQL 执行 通过多线程执行器异步执行。

结果归并

将多个执行结果集归并以便于通过统一的 JDBC 接口输出。结果归并包括流式归并、内存归并和使用装饰 者模式的追加归并这几种方式。

解析引擎

相对于其他编程语言,SQL 是比较简单的。不过,它依然是一⻔完善的编程语言,因此对 SQL 的语法进 行解析,与解析其他编程语言(如:Java 语言、C 语言、Go 语言等)并无本质区别。

抽象语法树

解析过程分为词法解析和语法解析。词法解析器用于将 SQL 拆解为不可再分的原子符号,称为 Token。并 根据不同数据库方言所提供的字典,将其归类为关键字,表达式,字面量和操作符。再使用语法解析器 将词法解析器的输出转换为抽象语法树。

例如,以下 SQL:

 SELECT id, name FROM t_user WHERE status = 'ACTIVE' AND age > 18

截屏2021-01-19 下午11.07.10.png

为了便于理解,抽象语法树中的关键字的 Token 用绿色表示,变量的 Token 用红色表示,灰色表示需要 进一步拆分。

最后,通过 visitor 对抽象语法树遍历构造域模型,通过域模型 (SQLStatement) 去提炼分片所需的 上下文,并标记有可能需要改写的位置。供分片使用的解析上下文包含查询选择项(Select Items)、表信 息(Table)、分片条件(Sharding Condition)、自增主键信息(Auto increment Primary Key)、排序信 息(Order By)、分组信息(Group By)以及分⻚信息(Limit、Rownum、Top)。SQL 的一次解析过程 是不可逆的,一个个 Token 按 SQL 原本的顺序依次进行解析,性能很高。考虑到各种数据库 SQL 方言的 异同,在解析模块提供了各类数据库的 SQL 方言字典。

SQL 解析引擎

  • 历史

SQL 解析作为分库分表类产品的核心,其性能和兼容性是最重要的衡量指标。ShardingSphere 的 SQL 解 析器经历了 3 代产品的更新迭代。

第一代 SQL 解析器为了追求性能与快速实现,在 1.4.x 之前的版本使用 Druid 作为 SQL 解析器。经实际 测试,它的性能远超其它解析器。

第二代 SQL 解析器从 1.5.x 版本开始,ShardingSphere 采用完全自研的 SQL 解析引擎。由于目的不同, ShardingSphere 并不需要将 SQL 转为一颗完全的抽象语法树,也无需通过访问器模式进行二次遍历。它 采用对 SQL 半理解的方式,仅提炼数据分片需要关注的上下文,因此 SQL 解析的性能和兼容性得到了进 一步的提高。

第三代 SQL 解析器从 3.0.x 版本开始,尝试使用 ANTLR 作为 SQL 解析引擎的生成器,并采用 Visit 的方 式从 AST 中获取 SQL Statement。从 5.0.x 版本开始,解析引擎的架构已完成重构调整,同时通过将第一 次解析的得到的 AST 放入缓存,方便下次直接获取相同 SQL 的解析结果,来提高解析效率。因此我们建 议用戶采用 PreparedStatement 这种 SQL 预编译的方式来提升性能。

功能点

• 提供独立的 SQL 解析功能

• 可以非常方便的对语法规则进行扩充和修改 (使用了 ANTLR)

• 支持多种方言的 SQL 解析

数据库 支持状态
MySQL 支持,完善
PostgreSQL 支持,完善
SQLServer 支持
Oracle 支持
SQL92 支持

路由引擎

根据解析上下文匹配数据库和表的分片策略,并生成路由路径。对于携带分片键的 SQL,根据分片键的 不同可以划分为单片路由 (分片键的操作符是等号)、多片路由 (分片键的操作符是 IN) 和范围路由 (分片 键的操作符是 BETWEEN)。不携带分片键的 SQL 则采用广播路由。

分片策略通常可以采用由数据库内置或由用戶方配置。数据库内置的方案较为简单,内置的分片策略大 致可分为尾数取模、哈希、范围、标签、时间等。由用戶方配置的分片策略则更加灵活,可以根据使用方 需求定制复合分片策略。

分片路由

用于根据分片键进行路由的场景,又细分为直接路由、标准路由和笛卡尔积路由这 3 种类型。

直接路由

满足直接路由的条件相对苛刻,它需要通过 Hint(使用 HintAPI 直接指定路由至库表)方式分片,并且 是只分库不分表的前提下,则可以避免 SQL 解析和之后的结果归并。因此它的兼容性最好,可以执行包 括子查询、自定义函数等复杂情况的任意 SQL。直接路由还可以用于分片键不在 SQL 中的场景。例如,设 置用于数据库分片的键为 3

hintManager.setDatabaseShardingValue(3);

假如路由算法为 value % 2,当一个逻辑库 t_order 对应 2 个真实库 t_order_0 和 t_order_1 时, 路由后 SQL 将在 t_order_1 上执行。下方是使用 API 的代码样例:

String sql = "SELECT * FROM t_order"; try (
HintManager hintManager = HintManager.getInstance(); Connection conn = dataSource.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) {
hintManager.setDatabaseShardingValue(3); try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) { //...
} }
}

标准路由

标准路由是 ShardingSphere 最为推荐使用的分片方式,它的适用范围是不包含关联查询或仅包含绑定表 之间关联查询的 SQL。当分片运算符是等于号时,路由结果将落入单库(表),当分片运算符是 BETWEEN 或 IN 时,则路由结果不一定落入唯一的库(表),因此一条逻辑 SQL 最终可能被拆分为多条用于执行的 真实 SQL。举例说明,如果按照 order_id 的奇数和偶数进行数据分片,一个单表查询的 SQL 如下:

SELECT * FROM t_order WHERE order_id IN (1, 2);

那么路由的结果应为:

SELECT * FROM t_order_0 WHERE order_id IN (1, 2); 
SELECT * FROM t_order_1 WHERE order_id IN (1, 2);

绑定表的关联查询与单表查询复杂度和性能相当。举例说明,如果一个包含绑定表的关联查询的 SQL 如 下:

 SELECT * FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE order_ id IN (1, 2);

那么路由的结果应为:

SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE order_id IN (1, 2);

可以看到,SQL 拆分的数目与单表是一致的。

笛卡尔路由

笛卡尔路由是最复杂的情况,它无法根据绑定表的关系定位分片规则,因此非绑定表之间的关联查询需 要拆解为笛卡尔积组合执行。如果上个示例中的 SQL 并未配置绑定表关系,那么路由的结果应为:

SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE order_id IN (1, 2);
SELECT * FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE order_id IN (1, 2);

笛卡尔路由查询性能较低,需谨慎使用。

广播路由

对于不携带分片键的 SQL,则采取广播路由的方式。根据 SQL 类型又可以划分为全库表路由、全库路由、 全实例路由、单播路由和阻断路由这 5 种类型。

全库表路由

全库表路由用于处理对数据库中与其逻辑表相关的所有真实表的操作,主要包括不带分片键的 DQL 和 DML,以及 DDL 等。例如:

SELECT * FROM t_order WHERE good_prority IN (1, 10);

则会遍历所有数据库中的所有表,逐一匹配逻辑表和真实表名,能够匹配得上则执行。路由后成为

SELECT * FROM t_order_0 WHERE good_prority IN (1, 10);
SELECT * FROM t_order_1 WHERE good_prority IN (1, 10); 
SELECT * FROM t_order_2 WHERE good_prority IN (1, 10); 
SELECT * FROM t_order_3 WHERE good_prority IN (1, 10);

全库路由:

全库路由用于处理对数据库的操作,包括用于库设置的 SET 类型的数据库管理命令,以及 TCL 这样的事 务控制语句。在这种情况下,会根据逻辑库的名字遍历所有符合名字匹配的真实库,并在真实库中执行 该命令,例如:

SET autocommit=0;

在 t_order 中执行,t_order 有 2 个真实库。则实际会在 t_order_0 和 t_order_1 上都执行这个命

令。

全实例路由

全实例路由用于 DCL 操作,授权语句针对的是数据库的实例。无论一个实例中包含多少个 Schema,每 个数据库的实例只执行一次。例如:

CREATE USER [email protected] identified BY '123';

单播路由

单播路由用于获取某一真实表信息的场景,它仅需要从任意库中的任意真实表中获取数据即可。例如:

DESCRIBE t_order;

t_order 的两个真实表 t_order_0,t_order_1 的描述结构相同,所以这个命令在任意真实表上选择执行一

次。

阻断路由

阻断路由用于屏蔽 SQL 对数据库的操作,例如:

USE order_db;

这个命令不会在真实数据库中执行,因为 ShardingSphere 采用的是逻辑 Schema 的方式,无需将切换数 据库 Schema 的命令发送至数据库中。

改写引擎

工程师面向逻辑库与逻辑表书写的 SQL,并不能够直接在真实的数据库中执行,SQL 改写用于将逻辑 SQL 改写为在真实数据库中可以正确执行的 SQL。它包括正确性改写和优化改写两部分。

正确性改写

在包含分表的场景中,需要将分表配置中的逻辑表名称改写为路由之后所获取的真实表名称。仅分库则 不需要表名称的改写。除此之外,还包括补列和分⻚信息修正等内容。

标识符改写

需要改写的标识符包括表名称、索引名称以及 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;

在这种最简单的 SQL 场景中,是否将 SQL 解析为抽象语法树似乎无关紧要,只要通过字符串查找和替换

就可以达到SQL改写的效果。但是下面的场景,就无法仅仅通过字符串的查找替换来正确的改写SQL了:

SELECT order_id FROM t_order WHERE order_id=1 AND remarks=' t_order xxx';

正确改写的 SQL 应该是:

SELECT order_id FROM t_order_1 WHERE order_id=1 AND remarks=' t_order xxx';

而非:

SELECT order_id FROM t_order_1 WHERE order_id=1 AND remarks=' t_order_1 xxx';

由于表名之外可能含有表名称的类似字符,因此不能通过简单的字符串替换的方式去改写 SQL。

下面再来看一个更加复杂的 SQL 改写场景:

 SELECT t_order.order_id FROM t_order WHERE t_order.order_id=1 AND remarks=' t_order xxx';

正确改写的 SQL 应该是:

SELECT order_id FROM t_order_1 WHERE order_id=1 AND remarks=' t_order xxx';

而非:

SELECT order_id FROM t_order_1 WHERE order_id=1 AND remarks=' t_order_1 xxx';

由于表名之外可能含有表名称的类似字符,因此不能通过简单的字符串替换的方式去改写 SQL。

下面再来看一个更加复杂的 SQL 改写场景:

  SELECT t_order.order_id FROM t_order WHERE t_order.order_id=1 AND remarks=' t_order xxx';

上面的 SQL 将表名作为字段的标识符,因此在 SQL 改写时需要一并修改:

 SELECT t_order_1.order_id FROM t_order_1 WHERE t_order_1.order_id=1 AND remarks=' t_order xxx';

而如果 SQL 中定义了表的别名,则无需连同别名一起修改,即使别名与表名相同亦是如此。例如:

  SELECT t_order.order_id FROM t_order AS t_order WHERE t_order.order_id=1 AND remarks=' t_order xxx';

SQL 改写则仅需要改写表名称就可以了:

 SELECT t_order.order_id FROM t_order_1 AS t_order WHERE t_order.order_id=1 AND remarks=' t_order xxx';

补列

需要在查询语句中补列通常由两种情况导致。第一种情况是 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,也会根 据表的元数据信息选择性补列。下面是一个较为复杂的 SQL 补列场景:

 SELECT o.* FROM t_order o, t_order_item i WHERE o.order_id=i.order_id ORDER BY user_id, order_item_id;

我们假设只有 t_order_item 表中包含 order_item_id 列,那么根据表的元数据信息可知,在结果归并时, 排序项中的 user_id 是存在于 t_order 表中的,无需补列;order_item_id 并不在 t_order 中,因此需要补 列。补列之后的 SQL 是:

 SELECT o.*, order_item_id AS ORDER_BY_DERIVED_0 FROM t_order o, t_order_item i WHERE o.order_id=i.order_id ORDER BY user_id, order_item_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 语句时,如果使用数据库自增主键,是无需写入主键字段的。但 数据库的自增主键是无法满足分布式场景下的主键唯一的,因此 ShardingSphere 提供了分布式自增主键 的生成策略,并且可以通过补列,让使用方无需改动现有代码,即可将分布式自增主键透明的替换数据库 现有的自增主键。分布式自增主键的生成策略将在下文中详述,这里只阐述与 SQL 改写相关的内容。举 例说明,假设表 t_order 的主键是 order_id,原始的 SQL 为:

INSERT INTO t_order (`field1`, `field2`) VALUES (10, 1);

可以看到,上述 SQL 中并未包含自增主键,是需要数据库自行填充的。ShardingSphere 配置自增主键后,

SQL 将改写为:

INSERT INTO t_order (`field1`, `field2`, order_id) VALUES (10, 1, xxxxx);

改写后的 SQL 将在 INSERT FIELD 和 INSERT VALUE 的最后部分增加主键列名称以及自动生成的自增主 键值。上述 SQL 中的 xxxxx 表示自动生成的自增主键值。

如果 INSERT 的 SQL 中并未包含表的列名称,ShardingSphere 也可以根据判断参数个数以及表元信息中 的列数量对比,并自动生成自增主键。例如,原始的 SQL 为:

INSERT INTO t_order VALUES (10, 1);

改写的 SQL 将只在主键所在的列顺序处增加自增主键即可:

INSERT INTO t_order VALUES (xxxxx, 10, 1);

分⻚修正

从多个数据库获取分⻚数据与单数据库的场景是不同的。假设每 10 条数据为一⻚,取第 2 ⻚数据。在分 片环境下获取 LIMIT 10, 10,归并之后再根据排序条件取出前 10 条数据是不正确的。举例说明,若 SQL 为:

SELECT score FROM t_score ORDER BY score DESC LIMIT 1, 2;
截屏2021-01-19 下午11.09.03.png

截屏2021-01-19 下午11.09.32.png

越获取偏移量位置靠后数据,使用 LIMIT 分⻚方式的效率就越低。有很多方法可以避免使用 LIMIT 进行 分⻚。比如构建行记录数量与行偏移量的二级索引,或使用上次分⻚数据结尾 ID 作为下次查询条件的分 ⻚方式等。

分⻚信息修正时,如果使用占位符的方式书写 SQL,则只需要改写参数列表即可,无需改写 SQL 本身。

批量拆分

在使用批量插入的 SQL 时,如果插入的数据是跨分片的,那么需要对 SQL 进行改写来防止将多余的数据 写入到数据库中。插入操作与查询操作的不同之处在于,查询语句中即使用了不存在于当前分片的分片 键,也不会对数据产生影响;而插入操作则必须将多余的分片键删除。举例说明,如下 SQL:

INSERT INTO t_order (order_id, xxx) VALUES (1, 'xxx'), (2, 'xxx'), (3, 'xxx');

假设数据库仍然是按照 order_id 的奇偶值分为两片的,仅将这条 SQL 中的表名进行修改,然后发送至数 据库完成 SQL 的执行,则两个分片都会写入相同的记录。虽然只有符合分片查询条件的数据才能够被查 询语句取出,但存在冗余数据的实现方案并不合理。因此需要将 SQL 改写为:

 INSERT INTO t_order_0 (order_id, xxx) VALUES (2, 'xxx');
INSERT INTO t_order_1 (order_id, xxx) VALUES (1, 'xxx'), (3, 'xxx');

优化改写

优化改写的目的是在不影响查询正确性的情况下,对性能进行提升的有效手段。它分为单节点优化和流 式归并优化。

单节点优化

路由至单节点的 SQL,则无需优化改写。当获得一次查询的路由结果后,如果是路由至唯一的数据节点, 则无需涉及到结果归并。因此补列和分⻚信息等改写都没有必要进行。尤其是分⻚信息的改写,无需将 数据从第 1 条开始取,大量的降低了对数据库的压力,并且节省了网络带宽的无谓消耗。

流式归并优化

它仅为包含 GROUP BY 的 SQL 增加 ORDER BY 以及和分组项相同的排序项和排序顺序,用于将内存归 并转化为流式归并。在结果归并的部分中,将对流式归并和内存归并进行详细说明。

连接模式

从资源控制的⻆度看,业务方访问数据库的连接数量应当有所限制。它能够有效地防止某一业务操作过 多的占用资源,从而将数据库连接的资源耗尽,以致于影响其他业务的正常访问。特别是在一个数据库实 例中存在较多分表的情况下,一条不包含分片键的逻辑 SQL 将产生落在同库不同表的大量真实 SQL ,如 果每条真实 SQL 都占用一个独立的连接,那么一次查询无疑将会占用过多的资源。

从执行效率的⻆度看,为每个分片查询维持一个独立的数据库连接,可以更加有效的利用多线程来提升 执行效率。为每个数据库连接开启独立的线程,可以将 I/O 所产生的消耗并行处理。为每个分片维持一个 独立的数据库连接,还能够避免过早的将查询结果数据加载至内存。独立的数据库连接,能够持有查询 结果集游标位置的引用,在需要获取相应数据时移动游标即可。

以结果集游标下移进行结果归并的方式,称之为流式归并,它无需将结果数据全数加载至内存,可以有效 的节省内存资源,进而减少垃圾回收的频次。当无法保证每个分片查询持有一个独立数据库连接时,则 需要在复用该数据库连接获取下一张分表的查询结果集之前,将当前的查询结果集全数加载至内存。因 此,即使可以采用流式归并,在此场景下也将退化为内存归并。

一方面是对数据库连接资源的控制保护,一方面是采用更优的归并模式达到对中间件内存资源的节省,如 何处理好两者之间的关系,是 ShardingSphere 执行引擎需要解决的问题。具体来说,如果一条 SQL 在经 过 ShardingSphere 的分片后,需要操作某数据库实例下的 200 张表。那么,是选择创建 200 个连接并行 执行,还是选择创建一个连接串行执行呢?效率与资源控制又应该如何抉择呢?

针对上述场景,ShardingSphere 提供了一种解决思路。它提出了连接模式(Connection Mode)的概念, 将其划分为内存限制模式(MEMORY_STRICTLY)和连接限制模式(CONNECTION_STRICTLY)这两种 类型。

内存限制模式

使用此模式的前提是,ShardingSphere 对一次操作所耗费的数据库连接数量不做限制。如果实际执行的 SQL 需要对某数据库实例中的 200 张表做操作,则对每张表创建一个新的数据库连接,并通过多线程的 方式并发处理,以达成执行效率最大化。并且在 SQL 满足条件情况下,优先选择流式归并,以防止出现 内存溢出或避免频繁垃圾回收情况。

连接限制模式

使用此模式的前提是,ShardingSphere 严格控制对一次操作所耗费的数据库连接数量。如果实际执行的 SQL 需要对某数据库实例中的 200 张表做操作,那么只会创建唯一的数据库连接,并对其 200 张表串行 处理。如果一次操作中的分片散落在不同的数据库,仍然采用多线程处理对不同库的操作,但每个库的 每次操作仍然只创建一个唯一的数据库连接。这样即可以防止对一次请求对数据库连接占用过多所带来 的问题。该模式始终选择内存归并。

内存限制模式适用于 OLAP 操作,可以通过放宽对数据库连接的限制提升系统吞吐量;连接限制模式适 用于 OLTP 操作,OLTP 通常带有分片键,会路由到单一的分片,因此严格控制数据库连接,以保证在线 系统数据库资源能够被更多的应用所使用,是明智的选择。

自动化执行引擎

ShardingSphere 最初将使用何种模式的决定权交由用戶配置,让开发者依据自己业务的实际场景需求选 择使用内存限制模式或连接限制模式。

这种解决方案将两难的选择的决定权交由用戶,使得用戶必须要了解这两种模式的利弊,并依据业务场 景需求进行选择。这无疑增加了用戶对 ShardingSphere 的学习和使用的成本,并非最优方案。

这种一分为二的处理方案,将两种模式的切换交由静态的初始化配置,是缺乏灵活应对能力的。在实际的 使用场景中,面对不同 SQL 以及占位符参数,每次的路由结果是不同的。这就意味着某些操作可能需要使 用内存归并,而某些操作则可能选择流式归并更优,具体采用哪种方式不应该由用戶在 ShardingSphere 启动之前配置好,而是应该根据 SQL 和占位符参数的场景,来动态的决定连接模式。

为了降低用戶的使用成本以及连接模式动态化这两个问题,ShardingSphere 提炼出自动化执行引擎的思 路,在其内部消化了连接模式概念。用戶无需了解所谓的内存限制模式和连接限制模式是什么,而是交 由执行引擎根据当前场景自动选择最优的执行方案。

自动化执行引擎将连接模式的选择粒度细化至每一次 SQL 的操作。针对每次 SQL 请求,自动化执行引擎 都将根据其路由结果,进行实时的演算和权衡,并自主地采用恰当的连接模式执行,以达到资源控制和 效率的最优平衡。针对自动化的执行引擎,用戶只需配置 maxConnectionSizePerQuery 即可,该参数表 示一次查询时每个数据库所允许使用的最大连接数。

执行引擎分为准备和执行两个阶段。

准备阶段

顾名思义,此阶段用于准备执行的数据。它分为结果集分组和执行单元创建两个步骤。

结果集分组是实现内化连接模式概念的关键。执行引擎根据 maxConnectionSizePerQuery 配置项,结合 当前路由结果,选择恰当的连接模式。具体步骤如下:

  1. 将 SQL 的路由结果按照数据源的名称进行分组。
  2. 通过下图的公式,可以获得每个数据库实例在maxConnectionSizePerQuery的允许范围内,每个连接需要执行的 SQL 路由结果组,并计算出本次请求的最优连接模式。
截屏2021-01-19 下午11.10.01.png

在 maxConnectionSizePerQuery 允许的范围内,当一个连接需要执行的请求数量大于 1 时,意味着当前 的数据库连接无法持有相应的数据结果集,则必须采用内存归并;反之,当一个连接需要执行的请求数 量等于 1 时,意味着当前的数据库连接可以持有相应的数据结果集,则可以采用流式归并。

每一次的连接模式的选择,是针对每一个物理数据库的。也就是说,在同一次查询中,如果路由至一个 以上的数据库,每个数据库的连接模式不一定一样,它们可能是混合存在的形态。

通过上一步骤获得的路由分组结果创建执行的单元。当数据源使用数据库连接池等控制数据库连接数量 的技术时,在获取数据库连接时,如果不妥善处理并发,则有一定几率发生死锁。在多个请求相互等待 对方释放数据库连接资源时,将会产生饥饿等待,造成交叉的死锁问题。

举例说明,假设一次查询需要在某一数据源上获取两个数据库连接,并路由至同一个数据库的两个分表 查询。则有可能出现查询 A 已获取到该数据源的 1 个数据库连接,并等待获取另一个数据库连接;而查 询 B 也已经在该数据源上获取到的一个数据库连接,并同样等待另一个数据库连接的获取。如果数据库 连接池的允许最大连接数是 2,那么这 2 个查询请求将永久的等待下去。

聚合归并

无论是流式分组归并还是内存分组归并,对聚合函数的处理都是一致的。除了分组的 SQL 之外,不进行 分组的 SQL 也可以使用聚合函数。因此,聚合归并是在之前介绍的归并类的之上追加的归并能力,即装 饰者模式。聚合函数可以归类为比较、累加和求平均值这 3 种类型。

比较类型的聚合函数是指 MAX 和 MIN。它们需要对每一个同组的结果集数据进行比较,并且直接返回其 最大或最小值即可。

累加类型的聚合函数是指 SUM 和 COUNT。它们需要将每一个同组的结果集数据进行累加。 求平均值的聚合函数只有 AVG。它必须通过 SQL 改写的 SUM 和 COUNT 进行计算,相关内容已在 SQL 改写的内容中涵盖,不再赘述。

分⻚归并

上文所述的所有归并类型都可能进行分⻚。分⻚也是追加在其他归并类型之上的装饰器,ShardingSphere 通过装饰者模式来增加对数据结果集进行分⻚的能力。分⻚归并负责将无需获取的数据过滤掉。

ShardingSphere 的分⻚功能比较容易让使用者误解,用戶通常认为分⻚归并会占用大量内存。在分布式 的场景中,将 LIMIT 10000000, 10 改写为 LIMIT 0, 10000010,才能保证其数据的正确性。用戶 非常容易产生 ShardingSphere 会将大量无意义的数据加载至内存中,造成内存溢出⻛险的错觉。其实, 通过流式归并的原理可知,会将数据全部加载到内存中的只有内存分组归并这一种情况。而通常来说,进 行 OLAP 的分组 SQL,不会产生大量的结果数据,它更多的用于大量的计算,以及少量结果产出的场景。 除了内存分组归并这种情况之外,其他情况都通过流式归并获取数据结果集,因此 ShardingSphere 会通 过结果集的 next 方法将无需取出的数据全部跳过,并不会将其存入内存。

但同时需要注意的是,由于排序的需要,大量的数据仍然需要传输到 ShardingSphere 的内存空间。因此, 采用 LIMIT 这种方式分⻚,并非最佳实践。由于 LIMIT 并不能通过索引查询数据,因此如果可以保证 ID 的连续性,通过 ID 进行分⻚是比较好的解决方案,例如:

SELECT * FROM t_order WHERE id > 100000 AND id <= 100010 ORDER BY id; 

或通过记录上次查询结果的最后一条记录的 ID 进行下一⻚的查询,例如:

SELECT * FROM t_order WHERE id > 10000000 LIMIT 10;

使用规范

支持项

路由至单数据节点

• 100% 全兼容(目前仅 MySQL,其他数据库完善中)。 路由至多数据节点

全面支持 DML、DDL、DCL、TCL 和部分 DAL。支持分⻚、去重、排序、分组、聚合、关联查询(不支 持跨库关联)。以下用最为复杂的 DML 举例:

• SELECT 主语句

SELECT select_expr [, select_expr ...] FROM table_reference [, table_reference ...] [WHERE predicates]
[GROUP BY {col_name | position} [ASC | DESC], ...]
[ORDER BY {col_name | position} [ASC | DESC], ...]
[LIMIT {[offset,] row_count | row_count OFFSET offset}]

• select_expr

*|
[DISTINCT] COLUMN_NAME [AS] [alias] |
(MAX | MIN | SUM | AVG)(COLUMN_NAME | alias) [AS] [alias] | COUNT(* | COLUMN_NAME | alias) [AS] [alias]

• table_reference

tbl_name [AS] alias] [index_hint_list]
| table_reference ([INNER] | {LEFT|RIGHT} [OUTER]) JOIN table_factor [JOIN ON conditional_expr | USING (column_list)]

不支持项

路由至多数据节点

部分支持 CASE WHEN * CASE WHEN 中包含子查询不支持 * CASE WHEN 中使用逻辑表名不支持(请使 用表别名)不支持 HAVING、UNION (ALL)

部分支持子查询 * 子查询中使用 WHERE 条件时,必须包含分片键,当外层查询中也包含分片键时,子 查询和外层查询中的分片键必须保持一致

除了分⻚子查询的支持之外 (详情请参考分⻚),也支持同等模式的子查询。无论嵌套多少层,Sharding- Sphere 都可以解析至第一个包含数据表的子查询,一旦在下层嵌套中再次找到包含数据表的子查询将直 接抛出解析异常。

例如,以下子查询可以支持:

SELECT COUNT(*) FROM (SELECT * FROM t_order) o;
SELECT COUNT(*) FROM (SELECT * FROM t_order WHERE order_id = 1) o;
SELECT COUNT(*) FROM (SELECT * FROM t_order WHERE order_id = 1) o WHERE o.order_id = 1;

以下子查询不支持:

SELECT COUNT(*) FROM (SELECT * FROM t_order WHERE product_id = 1) o;
SELECT COUNT(*) FROM (SELECT * FROM t_order WHERE order_id = 1) o WHERE o.order_id = 2;

简单来说,通过子查询进行非功能需求,在大部分情况下是可以支持的。比如分⻚、统计总数等;而通过 子查询实现业务查询当前并不能支持。

由于归并的限制,子查询中包含聚合函数目前无法支持。
不支持包含 schema 的 SQL。因为 ShardingSphere 的理念是像使用一个数据源一样使用多数据源,因此

对 SQL 的访问都是在同一个逻辑 schema 之上。

对分片键进行操作

运算表达式和函数中的分片键会导致全路由。
假设 create_time 为分片键,则无法精确路由形如 SQL:

SELECT * FROM t_order WHERE to_date(create_time, 'yyyy-mm-dd') = '2019-01-01'; 由于ShardingSphere只能通过SQL 字面提取用于分片的值,因此当分片键处于运算表达式或函数中时,

ShardingSphere 无法提前获取分片键位于数据库中的值,从而无法计算出真正的分片值。 当出现此类分片键处于运算表达式或函数中的 SQL 时,ShardingSphere 将采用全路由的形式获取结果。

示例
支持的 SQL
不支持的 SQL
DISTINCT 支持情况详细说明 支持的 SQL

SQL

SELECT DISTINCT * FROM tbl_name WHERE col1 = ?

SELECT DISTINCT col1 FROM tbl_name
SELECT DISTINCT col1, col2, col3 FROM tbl_name SELECT DISTINCT col1 FROM tbl_name ORDER BY col1 SELECT DISTINCT col1 FROM tbl_name ORDER BY col2 SELECT DISTINCT(col1) FROM tbl_name

SELECT AVG(DISTINCT col1) FROM tbl_name
SELECT SUM(DISTINCT col1) FROM tbl_name
SELECT COUNT(DISTINCT col1) FROM tbl_name
SELECT COUNT(DISTINCT col1) FROM tbl_name GROUP BY col1 SELECT COUNT(DISTINCT col1 + col2) FROM tbl_name

SELECT COUNT(DISTINCT col1), SUM(DISTINCT col1) FROM tbl_name SELECT COUNT(DISTINCT col1), col1 FROM tbl_name GROUP BY col1 SELECT col1, COUNT(DISTINCT col1) FROM tbl_name GROUP BY col1

不支持的 SQL 分⻚

完全支持 MySQL、PostgreSQL 和 Oracle 的分⻚查询,SQLServer 由于分⻚查询较为复杂,仅部分支持。 分⻚性能

性能瓶颈

查询偏移量过大的分⻚会导致数据库获取数据性能低下,以 MySQL 为例: SELECT * FROM t_order ORDER BY id LIMIT 1000000, 10

这句 SQL 会使得 MySQL 在无法利用索引的情况下跳过 1000000 条记录后,再获取 10 条记录,其性能可 想而知。而在分库分表的情况下(假设分为 2 个库),为了保证数据的正确性,SQL 会改写为:

SELECT * FROM t_order ORDER BY id LIMIT 0, 1000010

即将偏移量前的记录全部取出,并仅获取排序后的最后 10 条记录。这会在数据库本身就执行很慢的情 况下,进一步加剧性能瓶颈。因为原 SQL 仅需要传输 10 条记录至客戶端,而改写之后的 SQL 则会传输 1,000,010 * 2 的记录至客戶端。

ShardingSphere 的优化
ShardingSphere 进行了 2 个方面的优化。

首先,采用流式处理 + 归并排序的方式来避免内存的过量占用。由于 SQL 改写不可避免的占用了额外的带 宽,但并不会导致内存暴涨。与直觉不同,大多数人认为 ShardingSphere 会将 1,000,010 * 2 记录全部 加载至内存,进而占用大量内存而导致内存溢出。但由于每个结果集的记录是有序的,因此 ShardingSphere 每次比较仅获取各个分片的当前结果集记录,驻留在内存中的记录仅为当前路由到的分片的结果集的当 前游标指向而已。对于本身即有序的待排序对象,归并排序的时间复杂度仅为 O(n),性能损耗很小。

其次,ShardingSphere 对仅落至单分片的查询进行进一步优化。落至单分片查询的请求并不需要改写 SQL 也可以保证记录的正确性,因此在此种情况下,ShardingSphere 并未进行 SQL 改写,从而达到节省带宽 的目的。

分⻚方案优化

由于 LIMIT 并不能通过索引查询数据,因此如果可以保证 ID 的连续性,通过 ID 进行分⻚是比较好的解 决方案:

SELECT * FROM t_order WHERE id > 100000 AND id <= 100010 ORDER BY id 或通过记录上次查询结果的最后一条记录的 ID 进行下一⻚的查询:

SELECT * FROM t_order WHERE id > 100000 LIMIT 10

分⻚子查询

Oracle 和 SQLServer 的分⻚都需要通过子查询来处理,ShardingSphere 支持分⻚相关的子查询。 • Oracle

支持使用 rownum 进行分⻚:

目前不支持 rownum + BETWEEN 的分⻚方式。 • SQLServer

支持使用 TOP + ROW_NUMBER() OVER 配合进行分⻚:

支持 SQLServer 2012 之后的 OFFSET FETCH 的分⻚方式:
SELECT * FROM t_order o ORDER BY id OFFSET ? ROW FETCH NEXT ? ROWS ONLY

目前不支持使用 WITH xxx AS (SELECT ...) 的方式进行分⻚。由于 Hibernate 自动生成的 SQLServer 分 ⻚语句使用了 WITH 语句,因此目前并不支持基于 Hibernate 的 SQLServer 分⻚。目前也不支持使用两 个 TOP + 子查询的方式实现分⻚。

• MySQL, PostgreSQL
MySQL 和 PostgreSQL 都支持 LIMIT 分⻚,无需子查询:

SELECT * FROM t_order o ORDER BY id LIMIT ? OFFSET ?

解析器

ShardingSphere 使用不同解析器支持 SQL 多种方言。对于未实现解析器的特定 SQL 方言,默认采用 SQL92 标准进行解析。

你可能感兴趣的:(ShardingSphere)