SQL 测试业界 Related Works

246 阅读8分钟

对于自研的 SQL 引擎而言,如何确保在面对看似无限组合可能的 SQL 语句集的查询时保持正确性,一直是一个难以解决的痛点。即使是业界成熟的数据库(如 SQLite、MariaDB、Postgresql、CockroachDB 等),也在一些新的测试思路下暴露出了未报告缺陷[8][9][10][11]。

由于 SQL 语法的组合、数据的组合、执行框架的实现等各种影响因素的可能性组合是一个庞大量级的数目,因此暂时并没有一种能够完美或接近完美地解决 SQL 测试的方法论。RDBMS 数据库 SQL 测试的其中一种切入框架思路是把整个问题分成两个部分:SQL Generation 和 Test Oracle。

SQL Generation 关注的是如何生成查询集合去做覆盖测试。除了静态生成外,在自动化测试中随机化生成是最普遍应用的思想。其核心是按照 SQL 语法规则(语法定义或抽象语法树 Abstract Syntax Tree(AST) )来生成合法的 SQL。如图1,实现中会在深搜或图的规则内随机游走来生成一个合法路径,拼接起来就是一个合法 SQL。

  • 朴素的随机化方法的代表包括 RAGS[2] 、SQLSmith[3] 等。RAGS 是早期(约 1998 年前后)用于测试 Microsoft SQL Server 的测试工具。RAGS 基于语法规则随机生成 SQL,发给多台独立 SQL Server 实例做测试,一旦有结果不一致则表示有 bug。SQLSmith 是一个更现代的随机化 SQL 生成工具,包括了随机生成 AST 、生成不同数据库方言的 SQL ,以及连接数据库测试的能力。多个数据库产品如 PostgreSQL、TiDB、CockroachDB 等使用或改造 SQLSmith 来满足其 SQL 测试需求。但是朴素的随机化会带来许多无效查询的问题,比如可能不少生成的 SQL 的筛选条件或 JOIN 连接条件过于随机导致无法查询出数据。
  • 为解决朴素随机化带来的过多无效查询的问题,学术界有一些围绕 Query-Aware (可以理解为生成工具需要进一步理解查询语句)的思路。一个还处在原型阶段的工具 ADUSA [4] 借助别的定义工具 Alloy Tool 来引入了对 SQL 某些部分环节的约束来收敛无效查询生成。比如当已知一个有效查询如 id < 3 时,ADUSA 会把 id 的条件生成分为两种范围的情况 (id < 3) 和 (id >= 3),而不是无脑地随机生成一个可能离 3 很远的整型。另一种常见的思路是限定常量和表数据的取值基数。这也是很多软件测试 Mocker 的常用方法,通过限定整型数值范围或提供预定义 Value 集合的方式来保证数据与查询的关联性和有效性。图 2 中展示了 IBM DB2 的 Test Data Generator 可以让用户自定义自己的 Value Library 来提供每种不同列类型的取值集合[5]。

Test Oracle 关注的是用于验证或对比的结果集。在传统静态的单测 SQL Suite 测试(比如 MySQL Test Run [6])中,Test Oracle 就是指人工生成的 result 文件。而在自动化动态测试中,SQL 是源源不断随机生成的,Test Oracle 的选取也更加多样化。前文提到的 RAGS 是早期数据库 Differential Test 的代表,即同一个查询发给多个数据库实例去测试,如果结果不一致则说明有缺陷。多实例差异测试除了可以部署多个同一版本的数据库,也可以对比同一产品不同型号的数据库来保证迭代的正确性。如果不同产品的 SQL 语法较为接近,也可以部署不同数据库产品来对比测试。Differential Test 在数据库测试中也有着比较明显的不足,对于同一版本的数据库实例(甚至包括不同版本的),它们可能会由于一样的不正确实现导致产生一样的结果错误;对于使用其他数据库产品做基准来对比,则实践中经常会面临一个巨大的挑战—— 不同 SQL 方言之间的兼容性问题。

为了解决 Test Oracle 的挑战,一些可自验证的方法被提出和实践。Manuel Rigger 于 2020 年和 2021 年在不同会议发表了四篇不同方法的论文[7],均为一些流行的工业级数据库产品发现了不少数量的缺陷。

  • PQS(Pivoted Query Synthesis)[8] 的核心思想是选取表中的一行数据,随机构造一个 WHERE 表达式保证包括该行数据,然后把这个 WHERE 带入 SQL 中执行并校验该行数据是否完整被包括在结果集中。PQS 的挑战在于如何构造有效的表达式,目前作者论文也只是通过强行添加 NOT / IS NULL 等方式把 false / null 的结果扭转为 true。这个思路看起来很粗暴,适应的查询集也有一定局限性,但事实上该方法累计报告了上百个 bugs,其中被认为工程较完备的 SQLite 确认的 bugs 就至少有 65 个。
  • NoREC(Non-optimizing reference engine construction)[9] 核心思想是通过黑盒的手段关闭一些优化来对比某些优化规则在优化器中优化前后是否结果一致,来检查代码中是否有潜在的逻辑错误。最主要的手段就是把 WHERE 条件的 filter 移到 SELECT 的 result fields 中。数据库优化器在处理 WHERE 的时候通常有不少可能的优化逻辑,包括但不限于常量折叠、表达式改写、谓词下推、access path 构建、索引选择、基数估计等;而表达式移到 result fields 后,通常数据库不会对其做特别的优化,以图 3 中的 SQL 为例,一般都会退化成最简单的扫表和解析表达式执行。

  • TLP(Ternary Logic Partitioning)[10] 基于结果集三值逻辑的分区思想。三值逻辑把 SQL 表达式结果分为 true/false/null 三种结果情况,SQL WHERE 匹配结果必须是 true (请注意不是 NOT false)。以图 4 为例,如果有个随机生成的查询 Q 和对应执行结果 RS(Q),最终我们期望能得到 WHERE cond IS TRUE UNION ALL WHERE cond is FALSE UNION ALL WHERE cond is NULL 的并集等于该查询不加 WHERE 的结果,如果不符合则说明有缺陷。

  • CERT (Cardinality Estimation Restriction Testing)[11] 主要是测试优化器基数估计的。CERT 把查询 A 通过规则改写成查询 B ,改写规则会明确知道 B的结果集是 A 的结果子集,比如 A = LEFT/RIGHT/FULL JOIN 、B = INNER JOIN 或 A = SELECT ALL 、B = SELECT DISTINCT 等。CERT 是要求数据库统计信息稳定后(比如 ANALYZE TABLE 之后),通过比较估计出来的基数 ESTIMATE_COUNT(RS(A)) >= ESTIMATE_COUNT(RS(B)) 来确定优化器基数估计代码路径是否有异常。

PQS、NoREC、TLP 和 CERT 都被作者集成在一个 java 开发的 SQLancer 测试工具[12]中。作者利用 SQLancer 为不少成熟的工业级数据库发现了不少缺陷。同时一些新兴数据库产品如 doris、Databends 等也借助 SQLancer 来保证他们的 SQL 正确性。

上述相关研究成果都是基于黑盒测试的前提去开展的,原因是一方面测试研究人员也难以深入数据库的实现细节去定制化的做可以落地的改动;另一方面白盒测试工作的普适性和通用性较局限,每个数据库都得定制做类似的方案,且有些白盒改动由于内部实现差异巨大很可能无法适配迁移到别的数据库引擎。


References:

[1] 脱敏。

[2] Donald R Slutz. 1998. Massive stochastic testing of SQL. In VLDB, Vol. 98. 618622.

[3] SQLsmith - 开源随机SQL query生成器(1) / github.com/anse1/sqlsm…

[4] Abdul Khalek S, Elkarablieh B, O. Laleye Y, Khurshid S. Query-Aware Test Generation Using a Relational Constraint Solver. In:2008 23rd IEEE/ACM International Conference on Automated Software Engineering. IEEE; 2008:238-247. doi:10.1109/ASE.2008.34

[5] Test data generator for DB2 sample database creation or data scrambling with DTM Data Generator

[6] dev.mysql.com/doc/dev/mys…

[7] 数据库进阶测试三部曲 - 从 PQS 到 NoREC 再到 TLP

[8] Rigger, M., & Su, Z. (2020). Testing database engines via pivoted query synthesis. In 14th USENIX Symposium on Operating Systems Design and Implementation ( OSDI 20) (pp. 667-682).

[9] Rigger, M., & Su, Z. (2020, November). Detecting optimization bugs in database engines via non-optimizing reference engine construction. In Proceedings of the 28th ACM Joint Meeting on European Software Engineering Conference and Symposium on the Foundations of Software Engineering (pp. 1140-1152).

[10] Rigger, M., & Su, Z. (2020). Finding bugs in database systems via query partitioning. Proceedings of the ACM on Programming Languages, 4(OOPSLA), 1-30.

[11] Ba, J., & Rigger, M. (2023). Finding Performance Issues in Database Engines via Cardinality Estimation Testing. arXiv preprint arXiv:2306.00355.

[12] github.com/sqlancer/sq…

[13] SQL优化器-RBO与CBO分别是什么 - JasonCeng - 博客园

[14] Graefe, G. (1995). The cascades framework for query optimization. IEEE Data Eng. Bull. , 18(3), 19-29.