“世界上没有完美的数据库,也许Oracle是个例外”,前段时间有DBA讨论本地化替换时说.的确,Oracle算是一个比较完善的数据库产品,但是现在很多用户都面临着从Oracle数据库迁移到其他数据库的问题。中国电信已经宣布,将在今年年底前将所有Oracle数据库全部移除,并用国产或开源数据库取而代之。这周在和中国电信的朋友沟通的时候,他们说他们已经完成了数百个系统从Oracle数据库的迁移,这个任务最晚要到8月份才能完成。也有一些企业怕遇到坑,所以还在研究、认证、测试、分析等过程中。其实做决定之前还是要多加慎重。10年前中国电信提出用开源数据库替代Oracle的时候,分析了MYSQL和PG,我也参与了部分工作。当时通过对比MYSQL和PG,我们最后的分析结果是:如果要迁移计费系统,MYSQL比PG好。当然,这个分析并不是说MYSQL会彻底碾压PG,只是针对计费、记账等系统场景而言。PG扩容和VACUUM对系统的稳定运行影响较大,相对来说风险更大。事实上,我们也看不到太远。即使我们选择了一个好的数据库,在迁移过程中,甚至在迁移完成后的长期运行中,我们还是会遇到很多坑。有些问题可能是数据库基础设施从娘胎里带来的,不能立即解决。如果您的应用程序对此类问题很敏感,并且如果不加以解决,那将是非常悲惨的,这将导致大问题。就在昨天上班的时候,一个客户遇到了一个国内数据库的问题。他们有一条执行频率很高的SQL,整体成本很高。他们希望通过indexonlyscan来降低成本。但是索引创建后,执行计划还是没有使用indexonlyscan,还需要回到表的执行计划。我以前从未遇到过这种问题。正好这个国产数据库是基于OpenGauss2.0的。我们的测试环境有OpenGauss2.0和3.0环境。所以我先在opengauss2.0环境下做了一个测试。其实openGauss是不支持Coveringindex的。在openGauss2.0上,当我们创建Covering索引时,会报错:openGauss2.0不支持此语法,openGauss3.0类似,但报错信息变了:在openGauss3.0上,我没有看到仅索引扫描的预期执行计划。于是就这个问题和网上的朋友交流了一下。他刚好研究了一下,立马指出这是visibilitymap的问题。为了支持MVCC,PG8.4引入了visibilitymap。但是,VM文件不会实时更新。因此,如果页面在VM中不可见,则必须将其返回到表中,因为索引中没有数据行可见性的标识数据,因此无法使用IndexOnlyScan。为了进一步确认这个问题,我在PG11的社区版上进行了测试。droptabletest_covering;createtabletest_covering(idserial,nametext,valint);创建索引idx_test_coveringontest_covering(id)include(val);insertintotest_covering(name,val)select'test'||generate_series(1,10000),(random()*100)::int%100;分析test_covering;更新test_coveringsetval=val+1;selectrelallvisiblefrompg_classwhererelname='test_covering';selectrelallvisiblefrompg_classwhererelname='test_covering';explain(analyzetrue,bufferstrue)selectvalfromtest_coveringwhereid>=10andid<100;vacuumtest_covering;selectrelallvisiblefrompg_classwhererelname='test_covering';解释(分析为真,bufferstrue)selectvalfromtest_coveringwhereid>=10且ID<100;我们在PG11上看到了预期的执行计划,因为PG数据行的可见性信息只存储在表数据中,而索引中没有这些信息,所以我们在做IndexOnlyScan有时,如果VM不是及时更新,您必须返回表格以获取准确信息。在VACUUM之前执行的查询中,HEAPFETCHES为180,说明虽然执行计划是IndexOnlyScan,但是返回了180条数据到表中。当页面信息在VM中已经更新后,这些页面上的记录就不需要“还表”了。因此,在VACUUM之后,VM被更新。此时HEAPFETCHES变为0,表示没有回表操作。因为VM文件的大小远小于数据表的文件,不返回表会降低执行成本。从上面的例子我们也可以看出,不回表执行时间为0.037毫秒,回表后为0.203毫秒。差别还是挺大的。只要在PG11上做表分析,至少执行计划是IndexOnlyScan。为什么openGauss上的执行计划没有选择IndexOnlyScan?刚才我们测试openGauss的时候,因为不支持CoveringIndex的问题,我们重写了SQL。重写后的SQL在PG11上是什么样子的?我们发现如果索引变成普通索引,PG上的执行计划和openGauss上的执行计划是一样的。但是如果我们做一个vacuum,执行计划就正确了,就不存在“回表”的问题了。从这个测试开始,我们再想想openGauss。openGauss数据库的CBO优化器是不是认为因为VM比较老,这个查询需要回表,所以没有选择IndexOnlyScan的执行计划?所以我们在openGauss上也做了VACUUM,但是VACUUM完成后,可见页数还是0,执行计划还是没有变化。过了一段时间,发现可见页数不为零,于是再次分析执行计划,发现执行计划变成了IndexOnlyScan。openGauss文档没有说明VM文件更新的问题,因为我们只能猜测openGauss的vacuum命令并没有更新VM文件,而VM文件的更新可能是通过其他机制完成的。由于这个问题,openGauss不支持ASTORE上的CoveringIndex,以防止创建这样的索引。在大多数情况下,IndexOnlyScan的执行计划也是不可用的。但是我们在openGauss的相关文档中并没有找到这方面的说明。上次实验我们使用了openGauss3.0的USTORE功能,因为刚才看到openGauss支持USTORE上的覆盖索引,请问是否可以使用Ustore来解决这个问题呢?droptabletest_covering;createtabletest_covering(idserial,nametext,valint)with(STORAGE_TYPE=USTORE);createindexidx_test_coveringontest_coveringusingubtree(id)include(val);insertintotest_covering(name,val)选择'测试'||generate_series(1,10000),(random()*100)::int%100;explain(analyzetrue,bufferstrue)selectvalfromtest_coveringwhereid>=10andid<100;analyzetest_covering;更新测试覆盖setval=val+1;explain(analyzetrue,bufferstrue)从test_covering中选择val,其中id>=10且id<100;正如我们所料,优化器在启用USTORE索引仅扫描时不需要VM时会正确选择。貌似在opengauss上使用USTORE可以完美解决这个问题。然而,USTORE还不够成熟。USTORE上也有很多坑,比如USTORE表不支持回收站的问题,官方文档中没有提到,还有我们之前在USTORE上遇到的一些性能问题。从openGauss只支持USTORE上的覆盖索引,也可以看出华为openGauss在VM上可能存在一些问题。就像我们在测试中发现的那样,即使我们进行了vacuum,也无法立即更新VM数据。不及时更新VM会导致SQL语句的返回操作增多,使得覆盖索引的初衷无法实现。在使用数据库的过程中难免会遇到一些坑。我们在使用“完美数据库”——Oracle的时候不是经常遇到BUG吗?我们不怕遇到坑,就怕遇到坑后找不到解决办法,不知道坑到底是什么。国产数据库不仅仅是功能和性能差距的问题。更大的问题可能是在未来的长期维护中,运维知识、运维专家、运维工具的缺乏,可能会对国内数据库的发展造成更大的影响。.但不管怎样,迈出了第一步就没有理由后退,更不可能遇到坑就后退。公司在迈出第一步之前,应该未雨绸缪,安排一个团队来填坑,做到有备无患。
