DRDS (阿里云分布式关系型数据库服务,https://www.aliyun.com/product/drds)于 4 月 30 号发布了 5.3 版本,这是一个年度大更新。主要带来了以下特性:
- 性能提升。在大多数场景下(拆分键上的等值查询、读写分离等),同规格的吞吐量(最大 QPS)可以提升到之前的300%。
- 原生分布式事务。无需额外付费或者开通,不依赖第三方组件,即可执行分布式事务。提供柔性事务与 XA 两种实现。
- Outline。在无需改动程序的情况下,即可通过创建 Outline 的形式改变 SQL 的执行计划,例如指定索引、指定走主库或者备库等。
- 明确的 SQL 边界文档。在 SQL 边界内,进行了大量的随机测试,确保功能的稳定可靠。
- 更强大的分布式查询优化器。确保分布式 SQL 执行代价的最小化。
- 简洁易读的执行计划。提供一种新的执行计划显示格式,可以非常方便的看出 SQL 的执行策略。
1. 性能
DRDS 5.3,使用了 Plan Cache、协程、FastSQL 等技术,大幅提升了吞吐量,在同规格下,最大 QPS 提升到了之前的 300%。
例如,对于之前的版本,8C16G 的 DRDS 最大可以提供 2W/s 的 QPS;对于 DRDS 5.3,8C16G 的 DRDS 最大可以提供 6W+/s 的 QPS。
测试场景:
1.实例规格为入门版 8C16G
2.测试工具为 sysbench
3.后端 RDS 不存在瓶颈
4.测试 SQL:单表拆分键上的等值查询
SELECT * FROM t1 WHERE partition_key=?
5.持续加大并发,直至 DRDS CPU 接近 100%,并且 rt 在5ms左右
Plan Cache
DRDS 5.3 中,引入了 Plan Cache,大幅降低了 SQL 解析与查询优化的代价。DRDS 5.3 中,针对不同类型的 SQL,分成了多级 Plan Cache,其中,性能最高的是命中了一级 Plan Cache 的 SQL。无论参数取值如何,一定可以被下推到单分片执行的 SQL 会命中一级 Plan Cache,常见的形式有以下几种:
1.单表拆分键上的等值查询,例如:
SELECT * FROM t1 WHERE partition_key=?
2.拆分键上的等值 JOIN 查询,并且至少其中一个表带了拆分键上的等值条件,例如:
SELECT * FROM t1 JOIN t2 ON t1.partition_key = t2.partition_key WHERE t1.partition_key=?
3.拆分键上的等值关联子查询,并且其中内表或者外表带了拆分键上的等值条件,例如:
SELECT * FROM t1 WHERE EXSITS (SELECT 1 FROM t2 WHERE t1.partition_key = t2.partition_key) AND t1.partition_key=?
在应用中,更多的使用能够命中一级 Plan Cache 的 SQL,能更高的提升系统容量。
协程
DRDS 5.3 使用了 AliJDK 的 Wisp 协程。在业务逻辑相同的情况下,使用协程模型与使用线程模型相比,系统容量提升了 30% 左右。
更快的 Parser:FastSQL
DRDS 5.3 中的 Parser 部分,换成了从 Druid(https://github.com/alibaba/druid)剥离出来的 FastSQL。相对于老的 Parser,FastSQL 在 SQL 解析方面,比 antlr、javacc 等自动生成的 Parser 快了数十倍至数百倍,相对 DRDS 老版本的 Parser 带来了一倍的性能提升。FastSQL 近期会开源。
2. 原生分布式事务
DRDS 5.3 提供原生的分布式事务功能,有以下特点:
- 提供 柔性事务 与 XA 事务 两种事务方案供用户在不同的场景下进行选择。
- 不依赖任何第三方组件,能力集成在 DRDS Server 中,专有云无需额外资源进行部署。
- 无热点情况下性能线性可扩,无单点瓶颈。
- 无需额外开通,公有云上购买的实例即可立即使用,不产生额外费用。
DRDS 5.3 提供柔性事务和 XA 事务两种方案,一般情况下,当 DRDS 后端的 MySQL 为 5.7 及以上版本时,推荐使用 XA 事务。
柔性事务
DRDS 5.3 提供的最终一致方式执行的分布式事务称为柔性事务(Flexible Transactions)。
柔性事务放弃了隔离性,减小了事务中锁的粒度,使得应用能够更好的利用数据库的并发性能,实现吞吐量的线性扩展。异步执行方式可以更好的适应分布式环境,在网络抖动、节点故障的情况下能够尽量保障服务的可用性(Availability)。
DRDS 5.3 中开启柔性事务只需要一行代码:
SET drds_transaction_policy = 'flexible';
SHOW VARIABLES LIKE 'drds_transaction_policy';
+-------------------------+----------+
| VARIABLE_NAME | VALUE |
+-------------------------+----------+
| drds_transaction_policy | FLEXIBLE |
+-------------------------+----------+
1 row in set (0.07 sec)
除此之外,DRDS 柔性事务的使用方法和普通事务完全相同:应用首先用 SET autocommit = 0
和 SET drds_transaction_policy = 'flexible'
开启柔性事务;然后在同一个会话中执行事务的 SQL 语句 —— 最后当应用发起 commit
或 rollback
后,DRDS 将保证这些 SQL 语句执行的原子性:全部成功,或者全部失败。
XA 事务
DRDS 5.3 也支持 XA 事务,在柔性事务的基础上提供了强一致能力。由于 MySQL XA 实现机制的限制,我们要求只有在 DRDS 后端是 MySQL 5.7 版本以上才启用 XA 事务功能。
SET drds_transaction_policy = 'XA';
SHOW VARIABLES LIKE 'drds_transaction_policy';
+-------------------------+-------+
| VARIABLE_NAME | VALUE |
+-------------------------+-------+
| drds_transaction_policy | XA |
+-------------------------+-------+
1 row in set (0.07 sec)
DRDS XA 事务使用两阶段提交协议(XA Protocol)保护子事务的提交与回滚,消除了柔性事务的异步回滚问题。由于 XA Protocol 在提交与回滚阶段始终加锁,避免了事务结束前的脏读和覆盖,但是对性能有较大影响。
3. Outline
DRDS 5.3 提供 Outline 机制,允许用户在不修改程序与 SQL 的情况下,对特定类型的 SQL 的行为进行定制。简单说,Outline 可以将一个类型的源 SQL 在执行时动态的替换成另一个目标 SQL,目标 SQL 中可以带一些 HINT。
一些典型的应用场景:
- 使用
SLAVE HINT
将特定的SQL路由到只读实例执行:
CREATE OUTLINE O1 ON SELECT * FROM T1 WHERE ID=? TO SELECT /*+TDDL:SLAVE()*/ * FROM T1 WHERE ID=?
- 使用 MySQL 原生的
FORCE INDEX
为特定的 SQL 指定需要选择的索引:
CREATE OUTLINE O2 ON SELECT * FROM T1 WHERE ID=? TO SELECT * FROM T1 FORCE INDEX(index_xxx) WHERE ID=?
- 使用 DRDS 的 HINT 将特定的 SQL 路由到指定分片上执行:
CREATE OUTLINE O3 ON SELECT * FROM T1 WHERE ID=? TO SELECT /*+TDDL:node('0')*/ * FROM T1 WHERE ID=?
- DRDS 中的 Outline,可以对参数化的 SQL 进行匹配,也可以对特定参数的 SQL 进行匹配。例如,对于 SQL:
SELECT * FROM T1 WHERE ID=?
当 ID 取 1 时,需求到只读实例执行;当 ID 取其他值时,需求到主实例执行,则可以创建以下两个 Outline:
CREATE OUTLINE O1 ON SELECT * FROM T1 WHERE ID=1 TO SELECT /*+TDDL:SLAVE()*/ * FROM T1 WHERE ID=1;
CREATE OUTLINE O2 ON SELECT * FROM T1 WHERE ID=? TO SELECT /*+TDDL:MASTER()*/ * FROM T1 WHERE ID=?;
DRDS 会优先匹配带具体参数的 Outline。
DRDS Outline 的详细说明:https://help.aliyun.com/document_detail/71254.html
DRDS Hint 说明:https://help.aliyun.com/document_detail/71287.html
4. SQL 支持
SQL 兼容性方面,DRDS 5.3 最大的特点在于明确了 SQL 的边界,也即能够明确的说明哪些 SQL 支持、哪些 SQL 不支持。
DRDS 5.3 SQL 边界文档:https://help.aliyun.com/document_detail/71252.html。
一些重要的 SQL 类型:
- 子要查询方面,支持 Correlated Subqueries(不要求关联项一定是拆分键)、Derived Tables,暂不支持列子查询。更多子查询的支持范围参考:https://help.aliyun.com/document_detail/71295.html。
- 支持分布式 JOIN(不要求一定要带拆分键,不要求必须是拆分键上的 JOIN),暂不支持 STRAIGHT_JOIN 和 NATURAL JOIN。
- 支持大部分 MySQL 函数,主要暂不支持的为:全文检索函数、XML 函数、空间分析函数与 JSON 函数。
- UPDATE/DELETE 语句仅支持单表操作,不支持 UPDATE/DELETE 中包含 JOIN 以及子查询。
- 聚合函数支持 COUNT/SUM/MAX/MIN/AVG,GROUP BY 不要求FULL_GROUP_BY(https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html#sqlmode_only_full_group_by)。
- 支持逻辑 SQL 的
KILL
与SHOW PROCESSLIST
:https://help.aliyun.com/document_detail/71372.html。 - 支持
CREATE USER
创建更多用户,并使用GRANT
语句对用户权限进行授权:https://help.aliyun.com/document_detail/71356.html。 - 支持 PREPARE 协议、多语句与压缩协议。
5. Optimizer 与执行计划
DRDS 5.3 中,提供了非常丰富的分布式 SQL 优化策略,一些重要的例如:
- 对 Filter 的上拉、下压、推导等优化,确保 DRDS 可以准确的识别出 SQL 中可以下推的部分,这个能很大程度上提升 JOIN、子查询的性能,避免应该能下推却无法下推带来的性能损耗。
- 子查询的 SEMI-JOIN 优化。DRDS中,子查询会被改写为 SEMI-JOIN 进行优化,从而使其能够复用大量针对的 JOIN 的优化策略,提升性能和功能稳定性。
- 提供了一系列 Hint,允许调整执行计划的任意一个节点,结合 Outline 机制,达到不更改 SQL 也能对 SQL 进行性能优化的目的。
- 针对不同的场景,对排序与 Limit 进行优化,确保能将排序与 Limit 尽可能多的下推到存储节点上,保证传输的数据量最小。
DRDS 5.3 设计了全新的执行计划显示格式,相对老版本,具有以下特征:
- 收缩了分片的显示,执行计划不会因为涉及多个分片而臃肿庞大。
- 执行计划中包含了完整的执行策略,不存在二义性。
- 执行计划使用了标准的算子的语义,易于将标准的数据库知识应用到 DRDS 的查询优化中。
- 执行计划中将同时包含分布式执行计划以及存储分片上的执行计划(此特性 6 月份上线)。
- 提供 Optimizer Tracing 功能,能一步一步的展示出执行计划的优化过程,方便进行 SQL 调优。
-
通过执行计划可以清晰的判断出:
- SQL 需要在哪些分片上执行,是否跨分片
- JOIN、子查询、聚合、排序等操作是否能够下推
- JOIN、排序等所使用的算法是什么
例如,针对以下 SQL 的执行计划:
mysql> explain SELECT count(*), name FROM drds GROUP BY name ORDER BY count(*);
+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
| LOGICAL PLAN |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
| Project(count(*)="count(*)", name="name") |
| MemSort(sort="count(*) ASC") |
| Aggregate(group="name", count(*)="SUM(count(*))") |
| MergeSort(sort="name ASC") |
| LogicalView(tables="[00-03].drds", shardCount=4, sql="SELECT `name`, COUNT(*) AS `count(*)` FROM `drds` GROUP BY `name` ORDER BY `name`") |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
5 rows in set (0.13 sec)
从此执行计划中,我们可以获得以下信息:
- 需要在 00-03 供 4 个分片上执行物理 SQL (
LogicalView
算子):SELECTname
, COUNT(*) AScount(*)
FROMdrds
GROUP BYname
ORDER BYname
。 - Group By 操作基于排序实现,需要对
name
进行排序。由于每个分片上已经完成了 Order By 操作,因此分布式层需要对各个分片的数据做归并排序(MergeSort
算子)。 - 每个分组内,对 COUNT(*) 的结果做 SUM 操作,以汇总每个分片 COUNT(*) 的结果(
Aggregate
算子)。 - 使用内存排序,对 Aggregate 节点输出的 count(*) 进行排序(
MemSort
算子)。 - 最终结果集输出的是 count(*) 与 name 两列(
Project
算子)。
更多关于 DRDS 5.3 执行计划的介绍,请关注后续的文章。
What's NEXT
6 月底,DRDS 将发布 5.3.2,将会提供以下特性:
- 带计算能力的 DRDS 只读实例。可以直接在RDS主实例或者只读实例上,进行最高可提供 READ COMMITTED 级别的复杂 SQL(例如千万级的表的 JOIN 等)执行能力,并且随规格的提升,响应时间能进行近线性的扩展。
- 回收站,可对 DROP TABLE 操作进行闪回,方便在误删表的场景下快速对数据进行恢复。
- 基于事务的广播表写入。广播表将不再依赖任何第三方组件,可自行创建使用。
- 跨实例、机房、单元依然能保证全局唯一的主键服务。
欢迎大家持续关注 DRDS(阿里云分布式关系型数据库服务),详情:https://www.aliyun.com/product/drds。