当前位置: 首页 > 科技观察

一个优秀的优化器是在实践中磨练出来的

时间:2023-03-13 19:07:27 科技观察

在和国内一些数据库厂商的朋友交流的时候,总能听到他们说他们的优化器是高手设计的,充分利用了现代软硬件技术,所以才更强大比甲骨文。我不太认同这种说法,因为一个优秀的优化器设计能做到的在大型框架上是比较好的,而且对于一些常规的SQL语句更有效,而SQL语句的复杂度往往远远超过数据库设计师。按照想象,我们的数据库厂商往往低估了开发人员编写SQL的能力。那些天马行空的天才,会让最优秀的优化者也感到力不从心。几年前,一位朋友在做数据库迁移时遇到了SQL性能问题。这条SQL在Oracle上执行的非常高效,但是到了基于PG的国产数据库上,就慢得无法忍受了。.我们用一个简单的测试用例重现了这个问题。用户站点是内网系统,所以只能用模仿来给大家评测一下。我们使用dba_objects和dba_tables两个系统视图来创建两个物理表。然后执行这条语句:OracleDBA看到这条SQL会感到很疑惑,为什么他能写出这样一条SQL语句呢?程序员的大脑DBA很难理解。就是不知道我们数据库厂商懂不懂。但是,在实际的应用场景中,我们确实经常会遇到这样奇葩的SQL。我们在PG数据库上做一个类似的测试用例。我们使用PG_TABLES和PG_INDEXES这两个视图来创建t1/t2表。为了保险起见,我们会在建表后做一次真空分析。再看这条SQL的执行计划:这条SQL的执行速度看似还可以,但实际上真实环境中的数据是不一样的。从实施方案上看会出现什么问题。首先扫描T2表,找到一个数组,定义为SubPlan1,然后扫描T1表,过滤SubPlan1的结果,得到最终的数据。这个执行计划的问题其实非常明显。当T1/T2表很大时,这个查询会变得很慢。例如,我们将T2的大小增加到数万条记录。可以看出PG的执行计划变成了对T2表的索引扫描,这是优化器所做的有效优化。我们用同样的方法将T2表扩容到几十万条记录,看看会发生什么。执行计划还是一样,但是执行时间增加到了400多毫秒。如果数据库的IO性能有问题,t1表非常庞大,那么这个执行计划肯定有问题。在实际生产环境中,正是因为数据量大,才会出现性能问题。我们再来看一下Oracle的执行计划,可以看出这两个执行计划的差别非常大。Oracle的执行计划使用了一个Hash半连接,通过两次索引扫描得到半连接的两个半区数据,然后使用HASHUNIQUE检测内表数据。做一个10053trace,可以看到Oracle在编译这条SQL的时候,做了各种FPD和转换分析,最终找到了最优解。对这个分析过程感兴趣的可以自己做个10053看看。由于篇幅有限,这里就不做详细介绍了。整个跟踪文件接近6000行。这个执行计划可以说没有什么大问题。通过两个索引避免了两个大表的全表扫描,通过Hash半连接来控制整个JOIN的整体规模。对于一个优秀的数据库产品来说,随着应用规模的扩大,遇到的奇怪的SQL越来越多,它的优化器肯定会越来越强大。如果我们总是告诉用户不应该这样写SQL,不从优化器的角度去解决这些奇怪的SQL性能问题,那么我们的进步就会变得太慢,我们和Oracle之间的技术差距就会变得越来越宽。越来越大。对于这个案例,恰好前段时间和国内一家数据库厂商有过一些交流。他们旧版本的执行计划也不是很好。当时和厂商朋友分析他们的执行计划时,我认为虽然在T2的TABLESCAN上做了LIMIT(1)过滤,但是如果符合条件的记录位于大表的最后几行,那么扫描成本会很高。而最致命的是NestedloopJoinCartestan运算符。如果T1符合条件的数据很多,这条SQL的执行效率就会极低,甚至可能几个小时都执行不下去。最近测试了他们最新版本的产品,感觉新版本对优化器的提升能力还是比较大的。当表数据量不大时,执行计划扫描两个索引,然后进行MERGE半连接。当数据量大时,执行计划改为Hashright-semi-join。与Oracle的执行计划不同的是,较小的表T1采用全表扫描方式。虽然这个执行计划还有一些可以讨论的地方,但是不同的数据库可能因为全表扫描的成本计算不同,所以会有不同的选择。从两个版本执行计划的优化效果,我们也看到了国产数据库在核心能力上的进步。这种进步,恐怕只能通过不断的修炼来磨练。因此,我们也有理由相信,随着数据库创新工作的不断深入,我们的数据库产品会越来越好。