DRDS (阿里云分布式关系型数据库服务,www.aliyun.com/product/drd…)于 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(github.com/alibaba/dru…)剥离出来的 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 的详细说明:help.aliyun.com/document_de…
DRDS Hint 说明:help.aliyun.com/document_de…
4. SQL 支持
SQL 兼容性方面,DRDS 5.3 最大的特点在于明确了 SQL 的边界,也即能够明确的说明哪些 SQL 支持、哪些 SQL 不支持。
DRDS 5.3 SQL 边界文档:help.aliyun.com/document_de…。
一些重要的 SQL 类型:
- 子要查询方面,支持 Correlated Subqueries(不要求关联项一定是拆分键)、Derived Tables,暂不支持列子查询。更多子查询的支持范围参考:help.aliyun.com/document_de…。
- 支持分布式 JOIN(不要求一定要带拆分键,不要求必须是拆分键上的 JOIN),暂不支持 STRAIGHT_JOIN 和 NATURAL JOIN。
- 支持大部分 MySQL 函数,主要暂不支持的为:全文检索函数、XML 函数、空间分析函数与 JSON 函数。
- UPDATE/DELETE 语句仅支持单表操作,不支持 UPDATE/DELETE 中包含 JOIN 以及子查询。
- 聚合函数支持 COUNT/SUM/MAX/MIN/AVG,GROUP BY 不要求FULL_GROUP_BY(dev.mysql.com/doc/refman/…)。
- 支持逻辑 SQL 的
KILL与SHOW PROCESSLIST:help.aliyun.com/document_de…。 - 支持
CREATE USER创建更多用户,并使用GRANT语句对用户权限进行授权:help.aliyun.com/document_de…l。 - 支持 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(*)FROMdrdsGROUP BYnameORDER 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 操作进行闪回,方便在误删表的场景下快速对数据进行恢复。
- 基于事务的广播表写入。广播表将不再依赖任何第三方组件,可自行创建使用。
- 跨实例、机房、单元依然能保证全局唯一的主键服务。