从在流行的DBMS中发现400个错误的Bug Hunter中获得的经验教训

2020-10-18 03:59:52

查找逻辑错误是构建可靠的数据库管理系统(DBMS)的重要组成部分。但有时最明显的方法也不管用。您不能只查询几个数据库并比较结果。您需要一种更复杂的错误搜索方法。

这就是我们想让你见见曼纽尔·里杰的原因。在这段视频中,苏黎世理工大学博士后曼纽尔描述了使他和他的同事苏振东教授成为TiDB头号虫子猎手的技术。他们已经发现了50多个TiDB错误,当你考虑到他们与其他流行的DBMS一起工作时,他们已经发现了400多个错误。

Manuel评估了三种查找逻辑错误的技术。然后,他向我们演示了非优化参考引擎构造(NoREC),这是一种简单但并不明显的查找优化bug的方法。仅用这项技术,曼纽尔和他的同事就发现了150多个错误。

正如Manuel解释的那样,此方法的关键是重写查询语句,以便DBMS无法优化查询。虽然这种方法不直观,但它是发现优化错误的有效方法。

Manuel在TiDB DevCon 202上做了此演讲。单击视频链接观看Manuel带领您一步一步完成他的过程。你可以在这里看到幻灯片。

大家好。我叫曼纽尔·里杰。我是苏黎世理工学院的博士后研究员。我非常感谢PingCAP邀请我介绍我自己和我的工作。我来自奥地利,今年29岁。我喜欢徒步旅行、旅行和打乒乓球。

所以,我刚徒步旅行回来,现在我想花几分钟概述一下我们在数据库管理系统(DBMS)中寻找逻辑错误的工作,这是我和苏振东教授(他是苏黎世理工学院高级软件技术实验室的负责人)一直在合作的一个项目。

因此,在我们的工作中,我们测试了相当多的流行和广泛使用的DBMS,包括TiDB,到目前为止,我们已经发现了400多个错误。

关于TiDB,我们在TiDB漏洞搜索挑战计划中排名第一,到目前为止,我们已经为TiDB报告了50多个错误,还包括我们在此挑战之前报告的那些错误。

但现在让我们先退一步,谈谈我们的目标。因此,我们的目标是检测DBMS中的逻辑错误。

什么是逻辑错误?那么,我想用一个具体的例子来解释这一点。也就是说,我们有一个客户端应用程序,它向DBMS发送SQL查询,在我们的示例中是TiDB。然后,数据库管理系统应该检查所有相关记录。在本例中,我们有三条记录,其中两条条件(这里的谓词)的计算结果为true,另一条记录的条件计算结果为false。因此,我们预计返回的结果集包括两行;即条件计算为true的行。然而,在某些情况下,通过将查询发送到DBMS,我们可能会触发错误,在这种情况下,可能会发生返回的结果集不正确的情况,例如在这里的情况下,只提取一行而不是两行,我们将这些类型的错误称为逻辑错误。所以这些错误会导致错误结果集的计算。

我们怎么才能解决这个问题呢?嗯,最明显的方法是使用差异测试。在此上下文中的差异测试基本上意味着我们有一个查询生成器,我们使用它来生成发送给多个DBMS的查询。例如,不仅到TiDB,而且MariaDB和MySQL也是最接近的,或者是与TiDB的SQL方言最接近的DBMS。然后,这些DBMS中的每一个都会获取一个结果集,我们可以比较本例中的所有三个结果集,并检查它们是否都相同。如果没有,我们很可能在其中一个系统中发现了错误。不幸的是,差异测试不适用于DBMS。

我们为什么要这样说呢?首先,公共SQL核心相当小,并且DBMS差别很大。

现在,对于TiDB,您可能会争辩说,TiDB试图在很大程度上支持MySQL方言,但即使这样,我们也遇到了许多问题;例如,MySQL和TiDB共享共同的bug,在这种情况下不可能检测到这一点。例如,我们在这里打开了一个bug报告,其中一位TiDB开发人员提到MySQL也受到相同底层bug的影响。

因此,为了解决这个问题,我们一直在想办法来检测DBMS中的逻辑错误。第一种方法,也就是我今天演讲中重点介绍的方法,是非优化参考引擎构造(NoREC)。NoREC是一种简单但也不明显的查找具体优化错误的方法。

然后,我们一直在研究的另一种方法是枢轴查询合成(PQS),这是一种更强大的技术,但也更精细-在这一点上,我想指出的是,PingCAP实际上是第一家采用这种方法的公司。其他公司现在也在效仿,但周强(PingCAP效率改进团队经理)和他的团队-他们作为第一家公司已经成功实施了它,所以我想感谢他们所做的努力。然后,三元逻辑查询分区(TLP)是正在进行的工作,这是我们实际上用来查找我们为TiDB报告的错误的方法。

但现在让我们把注意力集中在NoREC上,这是一种简单但不明显的方法,我也可以在几分钟内解释一下。它允许我们在广泛使用的DBMS中发现150多个错误。

因此,正如我提到的,该方法特别旨在查找优化错误,这是逻辑错误的一个重要的子类别。也就是说,我们可以采用最初的激励示例,并假设bug是由TiDB的查询优化器中的bug引起的,这会导致从结果集中省略此行。

现在,我们想要的是:也就是说,我们希望有一个启用所有优化的TiDB版本,以及一个禁用所有优化的版本。因此,如果您熟悉像GCC或llvm这样的C/C++编译器,您可能知道这些优化标志,其中-O0基本上表示关闭了大多数优化,-O3表示打开了大部分优化。而且,如果您有类似的东西,我们可以直接比较结果集并找出查询优化器导致的错误。不幸的是,TiDB以及我们认为的其他DBMS对优化提供了有限的控制,因此只有几个选项或标志,这无助于检测大多数错误。

所以我们的想法是,我们可以重写查询,使DBMS无法对其进行优化,从而能够找到优化错误,而不是依赖于DBMS。

我们想出了下面的翻译例程。因此,在这里您可以看到原始查询,其中我们有WHERE行,在哪里提取条件计算为true的两行。现在,这里的想法是,我们基本上可以从WHERE子句中提取条件,并将其直接移动到SELECT之后。问题是:这现在有什么影响?好的,这基本上意味着这个谓词或条件在这些表中的每一行上都被求值。由于我们在这些表中有三条记录,即两条条件的计算结果为TRUE,一条条件的计算结果为FALSE,因此我们预计会返回三行的结果集,即两行的值为TRUE,一行的值为FALSE。在这里,我们基本上可以看到,对于两行,条件的计算结果为真。我们可以简单地将这两个进行比较,并在此示例中验证预期结果是计算出来的。

这里的直觉是DBMS不能有效地优化转换后的查询,因为DBMS通常试图聪明地只检查必要的记录,但这里必须对每条记录计算此条件,这会禁用大多数优化。因此,如果查询优化器中现在存在错误(在本例中,只提取了一行),我们能够检测到这些错误,因为谓词的计算结果为true的两行与实际提取的一行之间存在不匹配。这基本上已经是让我们能够检测到这么多错误的方法了。

该方法的具体实现:我们在SQLancer中实现了它,不久将在GitHub上提供,SQLancer在使用NoREC时执行以下步骤。首先,它随机生成一个数据库,然后生成优化的查询,从中派生未优化的查询,并通过检查优化的查询和未优化的查询是否相同来验证结果。

到此为止,我还想给您一个简短的演示,以实际演示我们的方法在实践中是有效的,并且可以在TiDB中发现我们已经报告的许多错误。

因此,您可以在这里看到错误报告。这是一个P1错误,所以是一个相当严重的错误,您可以在这里看到,我们创建了一个表,然后我们创建了一个视图,我们将其插入到表中,然后我们在这里有一个从表中提取记录的查询。

所以我现在要复制这些SQL语句。现在我要把它们喂给TiDB。让我们不太深入地研究查询实际应该做什么,但是让我们观察一下,现在返回了一个空的结果集。现在,让我们将其转换为未优化的查询。因此,我在这里添加这一点,以强制将谓词计算为布尔值。在这里,您实际上可以看到,返回一行的值为1,这基本上表示为真。因为我们在这里看到返回的是真值,所以我们可以推断,实际上这里的这个查询(上面的那个)应该返回单个记录,但事实并非如此,因此,我们应该能够在TiDB中检测到这个错误。

所以,我希望我能说服你们,这个简单但不明显的方法实际上对检测错误非常有用,我也希望我们正在进行的研究的概述会让你们感兴趣,我希望你们会在会议上玩得开心。在此,我要说,感谢你的收听和加油(#34;加油&34;)TiDB。