MySQL 是最流行的开源 SQL 数据库管理系统,由 Oracle Corporation 开发、分发和支持。InnoDB 是 MySQL 中一种通用的存储引擎,自 5.6 起成为默认的 MySQL 存储引擎,平衡了高可靠性和高性能。
Calcite 的 InnoDB 适配器允许你直接基于 InnoDB 数据文件查询数据,如下图所示,数据文件也称为 .ibd 文件。它利用了 innodb-java-reader。该适配器与 JDBC 适配器不同,后者映射 JDBC 数据源中的模式,需要 MySQL 服务器来提供响应。
1 | SQL query |
有了 .ibd 文件和相应的 DDL,InnoDB 适配器充当一个简单的 “MySQL 服务器”:它接受 SQL 查询,并尝试基于 innodb-java-reader 提供的 InnoDB 文件访问 API 来编译每个查询。它尽可能在 InnoDB 数据文件中直接进行投影、过滤和排序。
更重要的是,通过 DDL 语句,适配器是 “索引感知的”。它利用规则来选择要扫描的适当索引,例如,使用主键或辅助键来查找数据,然后尝试将某些条件下推到存储引擎。适配器还支持提示,以便用户可以告诉优化器使用特定的索引。
下面给出一个模型文件的基本示例,此模式从 MySQL “scott” 数据库读取:
1 | { |
sqlFilePath 是一个 DDL 文件列表,你可以通过在命令行中执行 mysqldump 来生成表定义:
1 | mysqldump -d -u<username> -p<password> -h <hostname> <dbname> |
/path/scott.sql 的文件内容如下:
1 | CREATE TABLE `DEPT`( |
ibdDataFileBasePath 是 .ibd 文件的父文件路径。
假设模型文件存储为 model.json,你可以通过 sqlline 连接到 InnoDB 数据文件以执行查询,如下所示:
1 | sqlline> !connect jdbc:calcite:model=model.json admin admin |
我们可以通过编写标准 SQL 查询所有员工:
1 | sqlline> select empno, ename, job, age, mgr from "EMP"; |
在执行此查询时,InnoDB 适配器使用主键扫描 InnoDB 数据文件 EMP.ibd,主键在 MySQL 中也称为聚类 B+ 树索引,并且能够将投影下推到底层存储引擎。投影可以减少从存储引擎获取的数据大小。
我们可以通过过滤来查找一个员工。InnoDB 适配器通过在 model.json 中提供的 DDL 文件检索所有索引。
1 | sqlline> select empno, ename, job, age, mgr from "EMP" where empno = 7782; |
InnoDB 适配器识别 empno 是主键,并使用聚类索引执行点查找,而不是全表扫描。
我们还可以对主键进行范围查询:
1 | sqlline> select empno, ename, job, age, mgr from "EMP" where empno > 7782 and empno < 7900; |
请注意,这种具有可接受范围的查询在 MySQL 使用 InnoDB 存储引擎时通常很高效,因为对于聚类 B+ 树索引,索引中接近的记录在数据文件中也接近,这有利于扫描。
我们可以通过辅助键查找员工。例如,在以下查询中,过滤条件是类型为 VARCHAR 的字段 ename。
1 | sqlline> select empno, ename, job, age, mgr from "EMP" where ename = 'smith'; |
InnoDB 适配器在 MySQL 中几乎所有常用数据类型上都能很好地工作,有关支持的数据类型的更多信息,请参阅 innodb-java-reader。
我们可以通过组合键查询。例如,给定 DEPTNO_MGR_KEY 的辅助索引。
1 | sqlline> select empno, ename, job, age, mgr from "EMP" where deptno = 20 and mgr = 7566; |
InnoDB 适配器利用匹配的键 DEPTNO_MGR_KEY 将过滤条件 deptno = 20 and mgr = 7566 下推。
在某些情况下,由于底层存储引擎 API 的限制,只能下推部分条件;其余条件保留在计划的其余部分。给定以下 SQL,只有 deptno = 20 被下推。
1 | select empno, ename, job, age, mgr from "EMP" where deptno = 20 and upsert_time > '2018-01-01 00:00:00'; |
innodb-java-reader 仅支持使用索引进行具有上下限的范围查询,而不完全支持 索引条件下推(ICP)。存储引擎返回一系列行,Calcite 从获取的行中评估其余的 WHERE 条件。
对于以下 SQL,有多个索引满足左前缀索引规则:可能的索引是 DEPTNO_JOB_KEY、DEPTNO_SAL_COMM_KEY 和 DEPTNO_MGR_KEY。InnoDB 适配器根据 DDL 中定义的顺序选择其中一个;只有 deptno = 20 条件被下推,其余的 WHERE 条件由 Calcite 的内置执行引擎处理。
1 | sqlline> select empno, deptno, sal from "EMP" where deptno = 20 and sal > 2000; |
通过辅助键访问行需要通过辅助索引扫描并检索回 InnoDB 中的聚类索引,对于 “大” 扫描,这将引入许多随机 I/O 操作,因此性能通常不够好。请注意,上面的查询可以通过使用 DEPTNO_SAL_COMM_KEY 索引来获得更高的性能,因为覆盖索引不需要检索回聚类索引。我们可以通过提示强制使用 DEPTNO_SAL_COMM_KEY 索引,如下所示。
1 | sqlline> select empno, deptno, sal from "EMP"/*+ index(DEPTNO_SAL_COMM_KEY) */ where deptno = 20 and sal > 2000; |
提示可以在 SqlToRelConverter 中配置,要启用提示,你应该在 SqlToRelConverter.ConfigBuilder 中为 TableScan 注册 index HintStrategy。索引提示对基本 TableScan 关系节点生效,如果有条件匹配索引,索引条件也可以下推。对于以下 SQL,虽然没有任何索引可以使用,但通过利用覆盖索引,性能比全表扫描更好,我们可以强制使用 DEPTNO_MGR_KEY 在辅助索引中扫描。
1 | sqlline> select empno,mgr from "EMP"/*+ index(DEPTNO_MGR_KEY) */ where mgr = 7839; |
如果排序与使用的索引的自然排序匹配,则可以下推。
1 | sqlline> select deptno,ename,hiredate from "EMP" where hiredate < '2020-01-01' order by hiredate desc; |
关于时区
MySQL 将 TIMESTAMP 值从当前时区转换为 UTC 进行存储,并从 UTC 转换回当前时区进行检索。因此在此适配器中,MySQL 的 TIMESTAMP 被映射到 Calcite 的 TIMESTAMP WITH LOCAL TIME ZONE。每个会话的时区设置可以在 Calcite 连接配置 timeZone 中配置,这告诉 MySQL 服务器 TIMESTAMP 值所在的时区。目前 InnoDB 适配器无法将属性传递到底层存储引擎,但你可以在 model.json 中指定 timeZone,如下所示。请注意,只有在连接配置中设置了 timeZone 并且它与 InnoDB 适配器运行的系统默认时区不同时,才需要指定该属性。
1 | { |
限制
innodb-java-reader 对 .ibd 文件有一些先决条件。
- 支持
COMPACT和DYNAMIC行格式。不支持COMPRESSED、REDUNDANT和FIXED。 innodb_file_per_table应设置为ON,innodb_file_per_table在 MySQL 5.6 及更高版本中默认启用。- 页面大小应设置为
16K,这也是默认值。
有关更多信息,请参阅先决条件。
在数据一致性方面,你可以将适配器视为一个简单的 MySQL 服务器,能够直接通过 InnoDB 数据文件查询,通过从 MySQL 卸载来转储数据。如果页面没有从 InnoDB 缓冲池刷新到磁盘,则结果可能不一致(.ibd 文件中的 LSN 可能小于内存中的页面)。InnoDB 就性能而言利用预写日志,因此没有可用于刷新所有脏页的命令。只有内部机制管理何时何地将页面持久化到磁盘,例如页面清理线程、自适应刷新等。
目前 InnoDB 适配器不知道 .ibd 数据文件的行数和基数,因此它依赖简单的规则来执行优化。如果将来底层存储引擎可以提供此类指标和元数据,则可以通过利用基于成本的优化将其集成到 Calcite 中。
写在最后
笔者因为工作原因接触到 Calcite,前期学习过程中,深感 Calcite 学习资料之匮乏,因此创建了 Calcite 从入门到精通知识星球,希望能够将学习过程中的资料和经验沉淀下来,为更多想要学习 Calcite 的朋友提供一些帮助。

