前言
在上一篇文章 ShardingSphere 联邦查询 GROUPING 聚合结果问题分析中,我们详细介绍了联邦查询引擎实现 GROUPING 聚合函数存在的问题,当时笔者曾提到 SQLancer 测试工具,它能够通过一些科学的方法来发现 SQL 逻辑问题,帮助提升联邦查询引擎的 SQL 支持度。本文将为大家详细介绍 SQLancer 测试工具,以及工具中内置的几种测试方法,然后我们会使用 SQLancer 工具,直接对联邦查询引擎进行测试,看看这个工具是否能够达到预期的测试效果,发现一些有价值的 SQL 漏洞。
什么是 SQLancer
SQLancer 项目,是由 Manuel Rigger 教授创建的,旨在发现数据库 SQL 引擎的逻辑 BUG,Manuel Rigger 教授曾在 Andy 组织的线上分享中介绍过 SQLancer,感兴趣的朋友可以观看 Finding Logic Bugs in Database Management Systems 视频了解。
SQLancer is a tool to automatically test Database Management Systems (DBMSs) in order to find bugs in their implementation. That is, it finds bugs in the code of the DBMS implementation, rather than in queries written by the user. SQLancer has found hundreds of bugs in mature and widely-known DBMSs.
根据官方文档介绍,SQLancer 是一款用于自动测试数据库管理系统的工具,用于查找数据库实现逻辑中的错误。它查找的是 DBMS 实现代码中的错误,而不是用户编写 SQL 中的错误。目前,SQLancer 已在众多主流的 DBMS 中发现了数百个错误。
下图展示了一个具体的逻辑错误:当用户输入 SQL 语句查询数据时,原本数据库中存在 2 条匹配的数据,但由于数据库的 SQL 引擎存在逻辑错误,最终只返回了 1 条数据。除了少返回数据行外,逻辑错误还包含:错误返回过滤条件外的结果,返回的数据行内容错误等。

数据库逻辑错误相比于语法错误危害性更大,语法错误会在执行阶段通过异常码反馈出来,中断当前的 SQL 执行,逻辑错误则会返回不正确的查询结果,用户无法通过任何信息识别出当前的逻辑错误,最终可能会导致严重的业务错误。
使用 SQLancer 测试工具,可以快速发现 SQL 逻辑问题,帮助提升 SQL 引擎的正确性,下面我们将分别介绍 SQLancer 常用的几种测试方法,看看这些方法是如何检测 SQL 逻辑问题。
PQS 测试方法
PQS 全称为 Pivoted Query Synthesis(枢轴查询合成),该方法详细的介绍可以参考论文——Testing Database Engines via
Pivoted Query Synthesis。它的核心思想是:随机选择一条记录(即枢轴记录),然后基于这条记录生成过滤条件和查询语句,再去 DBMS 中执行查询,如果 DBMS 返回的结果集没有包含这条记录,则说明 DBMS 存在问题。

上图展示了 PQS 测试方法的详细步骤,总结下来包括如下 7 个步骤:
- 随机生成一些表(
t0和t1表)和数据行(t0表c0:3, c1:TRUE数据行,t1表c0:-5数据行); - 从每张表中随机的选择一行数据,将这行数据作为基准行;
- 基于选择的基准行,随机生成表达式,并根据基准行的值计算出表达式结果;
- 根据表达式的计算结果调整表达式,直到表达式的计算结果为
TRUE,例如:上图步骤 3 中表达式计算结果为FALSE,步骤 4 中通过增加NOT将计算结果调整为TRUE; - 基于表达式随机生成查询语句,表达式使用在查询的
WHERE或者JOIN子句中,查询语句会返回基准行对应的列(SELECT t0.c0, t0.c1, t1.c0); - 将查询语句提交到 DBMS 中执行,获取返回的结果集;
- 校验结果集是否包含最初选择的基准行,如果不包含,说明 DBMS 可能存在缺陷。
PQS 测试方法是 SQLancer 支持的第一个测试方法,它支持 SQLite(3.28)、MySQL(8.0.16) 及 PostgreSQL(11.4) 数据库,由于该测试方法实现的工作量巨大,需要为每个 DB 实现 AST 解释器,并且无法支持聚合函数、窗口函数测试,目前 SQLancer 已经不再维护,官方推荐使用其他测试方法。如果大家对这个测试方法感兴趣,仍然可以使用如下的命令执行 PQS 测试,--oracle pqs 属性用于指定测试预言机的类型。
1 | java -jar sqlancer-*.jar --num-threads 4 --port 3306 --username root --password 123456 mysql --oracle pqs |
下图展示了使用 SQLancer PQS 方法测试 MySQL 的截图,测试出的不支持 SQL 可以在 target/logs 目录下查看。

NoREC 测试方法
NoREC 是 SQLancer 支持的第二个测试方法,全称是 Non-Optimizing Reference Engine Construction(非优化参考引擎构造),该方法的详细内容可参考论文——Detecting Optimization Bugs in Database Engines via Non-Optimizing Reference Engine Construction。
NoREC 的核心思想是:通过对比优化查询与非优化查询的结果差异,来检测 SQL 优化可能存在的漏洞。优化查询具体指:SELECT * FROM t0 WHERE φ,这条 SQL 可能会被 SQL 引擎优化,NoREC 测试方法会将这条 SQL 转换为非优化查询——SELECT (φ IS TRUE) FROM t0,将过滤条件移动到投影列中。通过 NoREC 测试方法,可以测试出数据库管理系统中的优化错误。

上图展示了 NoREC 测试方法的详细步骤,测试逻辑非常简单,具体如下:
- 随机生成一条较高优化潜力的 SQL(数据库中大多数优化都和过滤相关,因此生成包含 WHERE 条件的 SQL,预期将会被数据库管理系统进行优化),例如:
SELECT * FROM t0 WHERE φ; - 将优化 SQL 转换为无法优化的形式,具体来说,是将 WHERE 条件中的表达式移动到投影列中,例如:
SELECT (φ IS TRUE) FROM t0,这种查询缺乏 WHERE 条件,数据库管理系统必须检索所有记录; - 执行优化 SQL 和未优化 SQL 并比较结果集,如果未优化 SQL 返回 TRUE 的行数不等于优化 SQL 返回行数,则说明存在 BUG。
NoREC 测试方法支持 SQLite、MariaDB、PostgreSQL 和 CockroachDB 数据库,支持 WHERE、JOIN、ORDER BY 等子句测试,暂不支持 DISTINCT、窗口函数 测试。相比于 PQS,NoREC 增加了对聚合函数的支持,并且可以检测重复记录错误。执行如下的命令测试 NoREC 方法,通过 --oracle norec 参数指定 NoREC 方法:
1 | java -jar sqlancer-*.jar --num-threads 4 --port 3344 --username root --password 123456 mariadb --oracle norec |
下图展示了使用 SQLancer PQS 方法测试 MariaDB 的截图,测试出的不支持 SQL 可以在 target/logs 目录下查看。

TLP 测试方法
TLP 测试方法全称为 Ternary Logic Partitioning(三元逻辑分区),该方法的详细介绍可以参考论文——Finding bugs in database systems via query partitioning。
TLP 测试方法的核心思想是:将一个原始查询分解为多个分区查询,每个分区查询计算原始查询结果的一个子集,然后通过组合操作将这些子集合并,验证合并结果是否与原始查询结果一致,不一致则说明存在 BUG。从 TLP 的命名我们可以看出,在进行分区查询时,该方法采用了三元逻辑分区,基于 SQL 的三值逻辑(TRUE、FALSE、NULL),将原始查询分解为三个分区查询,分别对应谓词为 TRUE、FALSE 和 NULL 的情况。

上图展示了 TLP 测试方法的实现原理,具体测试流程如下:
- 随机生成数据库和查询语句;
- 根据原始查询生成分区查询,例如:
WHERE p、WHERE NOT p、WHERE p IS NULL; - 执行分区查询并合并查询结果;
- 与原始查询结果对比,不一致则发现 DB BUG。
如下表所示,TLP 测试方法相比 PQS(主要测试 WHERE)和 NoREC(主要测试 WHERE,部分聚合),能够支持更多的语法类型,包括:WHERE、GROUP BY、HAVING、聚合函数(如 MIN、MAX、SUM、COUNT、AVG)和 DISTINCT 查询。目前,TLP 测试方法已经支持了 SQLite、MySQL、PostgreSQL 等多种数据库,可以将 TLP 和其他测试方法结合,覆盖更多的测试场景。

执行如下的命令测试 TLP 方法,通过 --oracle tlp_where 参数指定 TLP 方法:
1 | java -jar sqlancer-*.jar --num-threads 4 --port 3306 --username root --password 123456 mysql --oracle tlp_where |
下图展示了使用 SQLancer TLP 方法测试 MySQL 的截图,测试出的不支持 SQL 可以在 target/logs 目录下查看。

DQE 测试方法
DQE 测试方法全称为 Differential Query Execution(差分查询执行),该方法由国内研究团队提出,详细论文内容可以参考——Testing Database Systems via Differential Query Execution。
DQE 测试方法的核心思想是:使用相同谓词(WHERE 条件)生成 SELECT、UPDATE 和 DELETE 语句,然后分别执行这 3 条语句,观察他们操作的数据行,如果操作的数据行不一致,则可能存在逻辑错误。

上图展示了 DQE 测试方法的详细流程,具体测试细节如下:
- 生成随机的库(
t1、t2)和表(c1、c2); - 生成随机谓词,例如:
NOT t1.c1; - 基于相同的谓词,生成一个查询元组,包含
SELECT、UPDATE和DELETE语句; - 在相同的数据库状态下,执行
SELECT、UPDATE和DELETE语句; - 获取
SELECT、UPDATE和DELETE语句执行结果; - 比较执行结果:
SELECT语句返回rowId,确定访问的数据行范围。UPDATE语句除了更新常规字段外,还额外更新updated为 1,执行后检查updated = 1的数据行及其rowId,确认修改行是否和SELECT一致。DELETE语句执行前记录所有rowId,执行后比较出删除的rowId范围,并和SELECT语句对比是否一致。
DQE 相比前文提到的其他测试方法,首次提出了针对 UPDATE 和 DELETE 语句逻辑错误的检测方法,并且支持 MySQL、MariaDB、CockroachDB 等数据库,完善了 SQLancer 测试工具覆盖的 SQL 场景。但是差分测试也存在一定的局限性,例如:无法测试出 SELECT、UPDATE 和 DELETE 语句同时出现相同错误的场景,不支持 DISTINCT、GROUP BY 等语法,以及包含非确定函数的场景(例如:RAND 函数)。
执行如下的命令测试 DQE 方法,通过 --oracle dqe 参数指定 DQE 方法:
1 | java -jar sqlancer-*.jar --num-threads 4 --port 3306 --username root --password 123456 mysql --oracle dqe |
下图展示了使用 SQLancer DQE 方法测试 MySQL 的截图,测试出的不支持 SQL 可以在 target/logs 目录下查看。

联邦查询测试实战
前文我们介绍了 SQLancer 常用的 4 种测试方法,通过这 4 种方法,可以较为全面地覆盖 SELECT、UPDATE 和 DELETE 语句,测试出 SQL 引擎的逻辑错误。本小节我们再来研究下,如何将 SQLancer 应用到 ShardingSphere 联邦查询功能中,帮助我们发现更多联邦查询的功能漏洞。
在开始测试联邦查询前,我们先了解下 SQLancer 工具如何使用,按照如下的命令克隆源码(Fork 仓库增加了对 ShardingSphere 的适配),然后进行编译打包,最后执行 java -jar sqlancer-*.jar --num-threads 4 sqlite3 --oracle NoREC 命令来开始测试。
1 | git clone git@github.com:strongduanmu/sqlancer.git |
执行 java -jar sqlancer-*.jar -h 命令可以查看 SQLancer 所有命令参数及说明,下面展示的内容进行了一些截取,重点展示 SQLancer 全局选项(options) 以及 MySQL 数据库的参数选项。从 Usage 格式可以看出(Usage: SQLancer [options] [command] [command options]),SQLancer 命令需要先指定全局选项 options(例如:--num-threads 4),然后再指定数据库专属命令 command(例如:sqlite3) 以及数据库专属选项 command options(例如:--oracle NoREC)。
1 | Usage: SQLancer [options] [command] [command options] |
由于 ShardingSphere 有逻辑库和存储单元(物理库)的概念,因此需要对 SQLancer 进行一些改造,才能测试 ShardingSphere 联邦查询。从上面的属性可以看出,我们新增了 storage-unit-host、storage-unit-port、storage-unit-username 和 storage-unit-password 属性,用于指定 ShardingSphere 存储单元的连接信息。而 SQLancer 原有的属性 host、port、username、password,则用于连接 ShardingSphere Proxy 接入端。
当 SQLancer 工具创建测试库时,不仅会在 Proxy 接入端创建逻辑库,还会额外通过 DistSQL 注册物理存储单元,然后再进行建表、生成数据和 SQL 测试等操作。我们执行如下的命令测试 ShardingSphere 联邦查询,--storage-unit-* 属性用于指定存储单元的连接信息,--num-threads 用来指定测试线程数,--num-tries 则用于控制发现多少个错误则终止测试。
1 | java -jar sqlancer-*.jar \ |
ShardingSphere 商业联邦查询的配置可以参考联邦查询-配置示例文档,首先在 Proxy 端执行以下的 DistSQL 开启联邦查询,ALL_QUERY_USE_SQL_FEDERATION 设置为 true,保证所有的 DML SQL 使用联邦查询引擎执行。
1 | ALTER SQL_FEDERATION RULE ( |
然后我们再开启统计信息收集功能,支持 SQLancer 对库、表、列等信息的查询,确保 SQLancer 测试逻辑能够正常运行。
1 | ALTER STATISTICS_STORAGE RULE ( |
开启统计信息收集,需要在 MySQL 中执行 Grant 语句给 STATISTICS_STORAGE Rule 中的账号赋权(如下数据库账号按照实际情况修改)。
1 | GRANT CREATE, SELECT, INSERT, UPDATE, DELETE, DROP ON sphereex_information_schema.* TO 'root'@'%'; |
再执行 DistSQL 设置统计信息收集间隔,并开启统计信息收集功能。
1 | SET DIST VARIABLE proxy_meta_data_collector_cron = '0 0/30 * * * ?'; |
首次开启统计信息功能,我们可以主动执行 REFRESH STATISTICS METADATA; 更新统计信息。
1 | REFRESH STATISTICS METADATA; |
配置完联邦查询功能,并开启统计信息收集后,我们可以执行前面的 SQLancer 命令,来测试下 ShardingSphere 联邦查询功能,很快我们就测试出了第一个联邦查询不支持的 Case——IS UNKNOWN 语法(异常 Case 会输出到 target/logs/mysql 目录下)。

SQLancer 提供的报错信息很全面,不仅包含了异常堆栈信息,还提供了复现这个异常所需的 SQL 操作步骤,我们依次执行 SQL 语句,就可以快速复现异常。
1 | java.lang.AssertionError: SELECT ALL t2.c1 AS ref0 FROM t2 WHERE NULL UNION ALL SELECT t2.c1 AS ref0 FROM t2 WHERE (NOT (NULL)) UNION ALL SELECT t2.c1 AS ref0 FROM t2 WHERE (NULL) IS UNKNOWN; |
另外,我们还可以根据 seed value: 1766925060265 来控制 SQLancer 生成的 Case,使用相同的种子值(通过参数 --random-seed 1766925060265 控制),可以生成相同的测试 Case,这样我们就可以稳定复现异常。
1 | java -jar sqlancer-*.jar \ |
SQLancer 测试工具功能强大,目前已经测试出一批 ShardingSphere 联邦查询不支持的 Case,由于篇幅限制,本文就不一一介绍了,笔者会根据这些异常信息,逐个进行分析和修复,不断提升 ShardingSphere 联邦查询的 SQL 支持度。大家如果有 SQL 引擎的测试需求,不妨也尝试下 SQLancer,相信它一定能够发现更多潜在的问题,帮助大家提升 SQL 引擎的稳定性。由于笔者也是初次探索和使用 SQLancer,如果文章有错误之处,或者其他 SQLancer 使用技巧,欢迎大家留言指导。
欢迎关注
欢迎关注「端小强的博客」微信公众号,会不定期分享日常学习和工作经验,欢迎大家关注交流。

