早些年搞Oracle的时候,最让人头疼的事情就是和共享池相关的,而共享池里遇到问题最多的事情大多数是和SQL编译相关的,关于十多年前这方面的往事我最近总想写一篇Oracle往事来给大家介绍一下。
Oracle共享池最初是为了全局共享SQL执行计划而设计的,全局共享执行计划可以最大限度的减少SQL解析,在高并发的系统中提升数据库的整体并发能力,这对于二三十年前的计算机硬件来说至关重要。其实除了Oracle之外,那个时代发展起来的商用数据库大多也支持全局SQL执行计划共享,比如SQL SERVER 的Parameterized Query Plan Caching。
全局SQL PLAN共享并不是数据库必须具备的功能,也不是所有的数据库都支持全局SQL PLAN共享,MySQL、Postgresql等开源数据库以及绝大多数国产数据库都是会话级共享SQL PLAN的。会话级共享SQL PLAN可能没有全局共享效果好,不过MySQL、PG这些数据库在设计之初并没有考虑在复杂业务场景下的超高并发执行问题,因此全局共享SQL PLAN并不是其涉及要点。采用会话级共享执行计划减少了数据字典相关的锁以及字典缓冲的闩锁争用,有效的降低了SQL解析器的复杂度,有助于数据库更稳定的运行,特别是在数据字典不太发生变化的场景中。随着现代硬件的快速发展,在大多数情况下,SQL解析所消耗的系统资源已经不成为主要的问题了,因此没有使用全局SQL PLAN CACHE的开源数据库在绝大多数高并发量执行的场景中也都能够胜任。在我所遇到的运维案例中,反而是采用全局执行计划共享的Oracle数据库经常在负载不算太高的场景中,比如每秒几万次执行的场景中,因为硬解析过多而触发了shared pool、Library cache 、cursor mutex等方面的争用,引发了较为严重的性能问题。
不管如何,共享执行计划(全局和会话级)确实有效的提高了数据库在SQL解析方面的效率,从而可以更好的支撑高并发场景和一些数据字典经常发生变更的高并发场景。不同的数据库共享SQL PLAN的实现方式差异很大,Oracle使用家传的共享池,随着Oracle数据库的发展,共享池已经变成了一个极其极其复杂的全局共享数据结构,不仅仅用于SQL PLAN共享,其全局内存堆(KGH)管理的模式是统一的。其他数据库一般使用相对简单一点的SQL PLAN CACHE来实现执行计划共享。
实现执行计划共享遇到的第一个问题是SQL使用非绑定变量的问题,最初的SQL共享完全是基于SQL文本的,其原理是对SQLTEXT做一个HASH函数,HASH值相同的SQL被认为是相同的,可以共享的。如果我们在SQL中直接使用常量值而没有使用绑定变量,那么SQL是无法共享的。Oracle在早期想要共享cursor,必须强制开发人员在编程时使用绑定变量。但是开发人员的水平参差不齐,经常会忘记这个开发规范。于是Oracle通过cursor_sharing参数对SQL进行签名处理。自动将相似的SQL进行归并,从而让SQL能够最大限度的共享。哪怕写程序是没有使用绑定变量,SQL解析器会自动帮你完成这个工作。
通过cursor_sharing自动处理非绑定变量的SQL或者使用绑定变量一定是件好事情吗?也不一定是这样的,如果某条SQL只有一个最佳的SQL PLAN,这是没有问题的。不过在现实中可能存在相同的SQL有多种最好的执行计划的情况存在。比如下面这个例子。
图片
当Object_id为20的时候,记录数有419万行数据,因此全表扫描是比较合理的执行计划。
图片
而当Object_id=21的时候,返回数据只有16行,因此此时使用索引范围扫描是比较合理的执行计划。而如果我们使用绑定变量,select object_name from t1 where object_id=:p,这样两种情况如果共享执行计划,那就明显是不合理的。
这种情况下如果共享执行计划,那么可能会出现十分怪异的情况。比如说同一条SQL有时候走索引,有时候走全表扫描。有时候突然就会大量SQL不走索引全部走执行计划了。甚至出现同一条SQL在不同RAC节点上,一个节点是走索引的,另一个节点上死活不走索引。其实明白了SQL共享与SQL解析的原理就很清楚了。在解析带有绑定变量或者通过cursor_sharing进行共享的SQL的时候,会进行绑定变量的窥探,编译时带入的参数就决定了执行计划最终走不走索引。而下一回同一条可共享的SQL执行的时候,就不会再去窥探参数了,因此就会导致不必要的SQL执行计划错误。
为了解决这个问题,Oracle推出了Adaptive Cursor Sharing(ACS)。在ACS技术的加持下如果存在数据偏斜,ACS 能够识别不同的绑定变量值具有不同的选择性,从而选择最为合理的执行计划。
当带有绑定变量的 SQL 语句首次被解析时,优化器会窥探绑定变量的值,并根据谓词的选择性生成一个执行计划,同时把该游标标记为 bind-sensitive(绑定敏感的)。当同样的 SQL 语句再次被执行时,优化器会比较当前绑定变量的值和之前的值,如果发现选择性有显著差异,优化器可能会创建一个新的子游标和执行计划,同时把该游标标记为 bind-aware(绑定感知的)。当同样的 SQL 语句再次被执行时,优化器会根据绑定变量的值的选择性范围,匹配最合适的子游标和执行计划,从而避免使用不适合的执行计划。
目前Oracle ACS可以在绝大多数场合下解决cursor共享的问题,不过ACS也存在一定的负面作用。比如会增加每次SQL执行的开销,同时会让一个CURSOR产生过多的不共享的执行计划,从而影响这个CURSOR的执行效率,增加MUTEX争用,严重时会引发系统性能问题。因此在某些应用场景中,用户会选择关闭ACS功能。
看到这里可能大多数朋友都会觉得我今天还是在炒Oracle ACS的冷饭,如果能看到这里的朋友,今天算是来对了。前面的近两千字的铺垫,只是为了让人更好地理解今天我想带给大家的一些干货。
今天要带给大家的第一点干货是和Oracle ACS相关的。虽然很多数据库不支持全局SQL PLAN CACHE,不过一般都支持会话级SQL PLAN CACHE。当某条SQL执行多次的时候,就不会再对这条SQL做解析,而直接复用缓冲中的执行计划了。那么与ORACLE 类似的问题出现了,如果数据是不均衡的,有些时候要走索引,有些时候需要走全表扫描怎么办?这种情况下,就会出现类似Oracle出现过的奇怪现象,某条SQL,有时候执行效率高,有时候执行效率低,而且我们无法控制。遇到这种情况,有时候可能就是因为SQL PLAN CACHE缓冲的执行计划不一定适合某个场景的SQL。
有些用过Oracle的朋友可能会想到解决这个问题的办法,那就不使用绑定变量,让SQL PLAN无法共享。其实这个办法在某些开源或者国产数据库中并不一定有效。比如PG数据库,默认就会对SQL进行签名,自动转换成绑定变量格式,就像Oracle数据库里设置了cursor_sharing=FORCE。而且这些数据库往往有不支持类似Oracle ACS的功能,因此使用非绑定变量来解决这个问题是无效的。
我们该如何解决这个问题呢?其实在Oracle占主导地位的时代,这个问题就已经有解了。如果我们关闭了ACS功能,但是确实存在某些场景中同样的SQL带入不同的参数时,需要有不同的执行计划,该如何处理呢。遇到这种情况,我们就只能通过在SQL语句上加上注释(/* PLAN B */),强制性的让优化器把这条SQL区分为两条不同的SQL。实际上,当Oracle还没有推出ACS功能的时候,我们就是这样在cursor_sharing=FORCE的数据库里纠正错误的执行计划的,这个方法对于MySQL、PG和一些国产数据库依然有效。
今天的第二点干货是关于分布式数据库的,与集中式数据库不同的是,分布式数据库上的 硬解析的成本要高得多,因此在分布式数据库中,尽可能要实现SQL PLAN CACHE。因此在分布式数据库上,因为SQL PLAN CACHE引起的SQL PLAN CACHE中的执行计划不适配的问题依然是存在的,并且在一些高负载的场景中,往往因为此类问题引发分布式集群范围的性能问题。这种情况下,如果你能够很快发现问题,并且将某个不合理的执行计划从SQL PLAN CACHE中清除掉,很快就能解决数据库集群的性能问题。
周五的时候,张瑞远先生就和我讨论过一个在OB上遇到的SQL PLAN CACHE引发的执行计划问题。经过分析发现OB默认的CURSOR_SHARING是FORCE,也就是说默认情况下,OB会自动对SQL进行签名,将没有使用绑定变量的SQL转化为使用绑定变量的格式。如果访问的数据存在较为严重的列倾斜现象,那么就会遇到SQL PLAN CACHE中的执行计划不适用的问题。经过和OB的朋友一起讨论,对OB中解决这个问题初步有了一个方案。首先在Oracle上使用 的PLAN B方案依然适用。如果某条SQL根据绑定变量不同,有少量的几种情况可以明确区分,那么PLAN B方案是可行的。如果数据倾斜问题比较复杂,不能简单的分类,那么在OB中可以通过HINT或者outlines,将这条SQL设置为不适用PLAN CACHE来规避这个问题。
在Oracle数据库中,也有类似的HINT,在数据库产品没有ACS功能或者关闭了ACS功能的 时候,这是一种十分有效的方法。绕开PLAN CACHE可以让本身就无法共享执行计划的SQL不要去干扰PLAN CACHE,可以更好的保护全局PLAN CACHE。
没想到今天写着写着就写多了,数据库的问题,每个小问题其实都够复杂的,摊开了讲,三五千字根本说不清楚,不过不要紧,花上几年时间,一个个研究清楚,你也就成了高手了。