一、介绍

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

二、正确性改写

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

1 标识符改写

1 需要改写的标识符包括表名称、索引名称以及 Schema 名称。

2 表名称改写是指将找到逻辑表在原始 SQL 中的位置,并将其改写为真实表的过程。表名称改写是一个典 型的需要对 SQL 进行解析的场景。

3 例子:
1)逻辑SQL: 假设该 SQL 配置分片键 order_id,并且 order_id=1 的情况,将路由至分片表 1。

  1. SELECT order_id FROM t_order WHERE order_id=1;

正确改写

  1. SELECT order_id FROM t_order_1 WHERE order_id=1;

2) SQL 中定义了表的别名,则无需连同别名一起修改

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

正确改写

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

3 索引名称是另一个有可能改写的标识符。在某些数据库中(如 MySQL、SQLServer),索引是以表为维度创建的,在不同的表中的索引是可以重名的;而在另外的一些数据库中(如 PostgreSQL、Oracle),索引是以数据库为维度创建的,即使是作用在不同表上的索引,它们也要求其名称的唯一性。

4 在 ShardingSphere 中,管理 Schema 的方式与管理表如出一辙,它采用逻辑 Schema 去管理一组数据源。因此,ShardingSphere 需要将用户在 SQL 中书写的逻辑 Schema 替换为真实的数据库 Schema。 ShardingSphere 目前还不支持在 DQL 和 DML 语句中使用 Schema。它目前仅支持在数据库管理语句中使用 Schema。
例如:

  1. SHOW COLUMNS FROM t_order FROM order_ds;

Schema的改写指的是将逻辑 Schema 采用单播路由的方式,改写为随机查找到的一个正确的真实 Schema。

2 补列

1 需要在查询语句中补列情况

1)第一种情况是 ShardingSphere 需要在结果归并时获取相应 数据,但该数据并未能通过查询的 SQL 返回。这种情况主要是针对 GROUP BY 和 ORDER BY。结果归并 时,需要根据 GROUP BY 和 ORDER BY 的字段项进行分组和排序,但如果原始 SQL 的选择项中若并未包 含分组项或排序项,则需要对原始 SQL 进行改写。

例子:原始 SQL 中并不包含需要在结果归并中需要获取的 user_id,需要对 SQL 进行补列改写,补列用在归并时。

  1. SELECT order_id FROM t_order ORDER BY user_id;

补列

  1. SELECT order_id, user_id AS ORDER_BY_DERIVED_0 FROM t_order ORDER BY user_id;

2)另一种情况是使用 AVG 聚合函数。在分布式的场景中,使用 avg1 + avg2 + avg3 / 3 计算平均值并 不正确,需要改写为 (sum1 + sum2 + sum3) / (count1 + count2 + count3)。这就需要将包含 AVG 的 SQL 改写为 SUM 和 COUNT,并在结果归并时重新计算平均值。

  1. SELECT AVG(price) FROM t_order WHERE user_id=1;

补列

  1. SELECT COUNT(price) AS AVG_DERIVED_COUNT_0, SUM(price) AS AVG_DERIVED_SUM_0 FROM t_
  2. order WHERE user_id=1;

3) 后一种补列是在执行 INSERT 的 SQL 语句时,如果使用数据库自增主键,是无需写入主键字段的。但 数据库的自增主键是无法满足分布式场景下的主键唯一的,因此 ShardingSphere 提供了分布式自增主键 的生成策略,并且可以通过补列,让使用方无需改动现有代码,即可将分布式自增主键透明的替换数据库 现有的自增主键。分布式自增主键的生成策略将在下文中详述,这里只阐述与 SQL 改写相关的内容。

使用分布式自增主键代替数据库主键

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

补列

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

3 分页修正

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

想要取得两个表中共同的按照分数排序的第 2 条和第 3 条数据,应该是 95 和 90。由于执 行的 SQL 只能从每个表中获取第 2 条和第 3 条数据,即从 t_score_0 表中获取的是 90 和 80;从 t_score_1 表中获取的是 85 和 75。因此进行结果归并时,只能从获取的 90,80,85 和 75 之中进行归并,那么结 果归并无论怎么实现,都不可能获得正确的结果。

  1. SELECT score FROM t_score ORDER BY score DESC LIMIT 1, 2;

image.png
正确的

  1. SELECT score FROM t_score ORDER BY score DESC LIMIT 0, 3

image.png

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

4 批量拆分

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

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

1 写入

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

正确数据

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

2 查询

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

正确数据(。ShardingSphere 暂时还未实现此改写策略)

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

目前的改写结果是:

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

5 优化改写

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

6 单节点优化

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

7 流式归并优化

它仅为包含 GROUP BY 的 SQL 增加 ORDER BY 以及和分组项相同的排序项和排序顺序,用于将内存归 并转化为流式归并。

三、流程图

image.png

image.png

image.png

image.png 05 改写引擎 - 图7