要说出什么是高速缓存,什么是结果高速缓存,如何在Oracle和其他数据库中进行创建,并不是很有趣并且很简单。 但是,当涉及到具体示例时,一切都呈现完全不同的颜色。
Alexander Tokarev (
shtock )基于案例构建了有关Highload ++ 2017的报告。 正是基于这种情况,他告诉了何时可以使用自制缓存,服务器端结果缓存的痛苦是什么,以及如何用客户端替换它,总的来说,他提出了许多有用的技巧来在Oracle中设置结果缓存。
关于演讲者: Alexander Tokarev在DataArt工作,处理与数据库有关的问题,包括从头开始构建系统和优化现有系统。
让我们从一些反问开始。 您是否使用过Oracle结果缓存? 您是否认为Oracle是适合所有场合的数据库? 根据亚历山大的经验,大多数人否定地回答最后一个问题,
一百个梦想家有一个梦想家 。 但是由于他的信仰,进步正在前进。
顺便说一句,Oracle已经拥有14个数据库-到目前为止14个-未来将发生什么尚不清楚。
如前所述,所有问题和解决方案将通过具体案例进行说明。 这将是DataArt项目的两种情况,以及一个第三方示例。
数据库缓存
首先,哪些高速缓存位于数据库中。 这里的一切都很清楚:
- 缓冲区高速缓存-数据高速缓存-数据页/数据块的高速缓存;
- 语句缓存-语句及其计划的缓存-查询计划的缓存;
- 结果缓存-行结果缓存-查询中的行;
- 操作系统缓存-操作系统缓存。
而且,结果缓存一般仅在Oracle中使用。 他曾经在MySQL中工作,但后来被英勇地淘汰了。 在PostgreSQL中不存在,它仅以一种形式或另一种形式存在于第三方pgpool产品中。
案例1:零售商库

上面是我们随附的产品图-存储库(Oracle 11、20 Tb,300个用户),它包含某种沉闷的报告,其中每5000条数据行有350个独特的产品。 获得它大约花费了20分钟,用户为此感到难过。
与其他所有人一样,此报告的演示文稿可以在Highload ++会议站点上获得。
该报告具有SELECT,JOIN和功能。 一个函数作为一个函数,一切都会好起来的,只有它计算出一个神秘的参数,称为“转移定价值”,它的作用时间为0.2 s-似乎什么都没有,但是它被调用的次数与表中的行数相同。 该函数具有400行SQL + PL / SQL,因为 该产品受到支持,因此很难进行更改。
出于相同的原因,不能使用result_cache。

为了解决该问题,我们使用
带有手工缓存的标准
方法 :照原样保留电路的前三个块,只需将sku_detail()函数重命名为sku_full()并分别声明一个关联数组:
- 关键是我们的SKU(商品),
- 值是计算出的转移转换价格。
我们使cache(sku)函数显而易见:如果关联数组中没有此类id,则将启动我们的函数,结果将被缓存,保存并返回。 因此,如果这样的id是,那么这一切都不会发生。 实际上,我们有了
按需缓存 。
因此,我们已将函数调用的数量减少到实际需要的数量。
报表处理时间减少到4分钟 ,所有用户都感觉良好。
手工缓存
从这张大型智能图片可以清楚地看出该系统的缺点和优势,这将是我们要解决的很多问题-这就是内存体系结构。

重要的是要了解集合位于哪个存储区中。 它们被放置在称为PGA的存储区中。 在与数据库的每个连接上实例化
程序全局区域 。 这是确定优点和缺点的原因,因为更多的连接-更多的内存,以及
昂贵的内存,服务器和管理员都很温柔。

- 优点:一切工作都非常快,非常容易,不需要配置,进程间参与也没有麻烦。
- 缺点是可以理解的:如果项目中禁止使用存储的逻辑,则不能使用它们,没有自动失效的机制,并且由于缓存中的内存是在一个数据库会话(而不是实例)中分配的,因此其消耗被夸大了 。 此外,在连接池用例的情况下,如果每个会话应该有不同的缓存,则必须记住刷新缓存。
基于物化视图和临时表的手工缓存还有其他选择,但是从它们来看,输入输出系统上的负担很大,因此在此不考虑它们。 它们更适用于其他数据库,在这些数据库中,通常通过将存储过程存储在某个中间表中并在访问繁重的请求之前从中获取数据来解决这些问题。 并且只有在未找到所需内容的情况下,才会调用初始请求。

上面是对这种缓存问题的方法的说明,该方法用于获取MsSQL中的相关产品列表。 通常,该方法相对相似,但是就获取数据和主要填充而言,它在数据库内存中均不起作用,因此,它
可能更慢 。
通常,积极使用自制的result_cache,但是数据库内result_cache是实现此任务的另一种方法。 它以及它如何无法快速获胜,我们将进一步考虑。
案例2.财务文件处理
因此,我们的第二种情况。

这是一个半自动化的财务文件处理系统-具有传统架构的沉闷企业,其中包括:
- 瘦客户
- 4,000个生活在世界各地的用户;
- 平衡器
- 2个用于计算业务逻辑的JBoss;
- 内存集群
- 核心Oracle;
- Oracle备份
该系统的许多任务之一是
建议的
计算 。

对于某些未通过系统自动识别的指标的文档,有一些指标是由先前的客户文档,类似行业或相似的获利能力提供的,同时将指标与公认的价值进行比较,以免提供太多指标。 重要的是,
文件是多语言的 。
用户选择所需的值,并为每个空行重复该操作。
简化后,该任务包括以下内容:文档以键值对的形式从不同的识别系统到达,并且参数在某处而不是某处被识别。 有必要确保最终用户可以处理文档并识别所有值。 该建议正是为了简化此任务,并考虑到:
- 多种语言-大约30种语言。 每种语言都有其自身的词干,同义词和其他功能。
- 该客户的先前数据,或者在没有该客户的情况下,来自同一行业的客户或利润相似的客户的数据。
实际上,这大约是12条非常复杂的规则。
初始假设:- 一次不超过100个用户;
- 2-3列用于识别;
- 100行。
根本没有高负载 -一切都很无聊。
因此,该发布了。 发生代码冻结,Java害怕触摸,处理文档至少需要5分钟。
他们来到数据库开发团队寻求帮助。 当然,因为
如果JVM的运行速度变慢,那么就需要更改或修复数据库 。

我们研究了文档,并意识到在键值对中,值经常重复-5-10次。 因此,我们决定使用数据库进行缓存,因为它已经过测试。
我们决定使用Oracle服务器端结果缓存,因为:
- 优化SQL的机会已经耗尽,因为它使用了Oracle全文搜索引擎。
- 缓存将用于重复的参数;
- 由于大多数推荐数据使用全文索引,因此每小时都会重新计算一次;
- 禁止使用PL / SQL 。
Oracle结果缓存
结果缓存-Oracle缓存结果-具有以下属性:
- 这是翻查所有查询结果的存储区;
- 读取一致,并自动失效;
- 对应用程序的更改最少。 您可以使应用程序根本不需要更改;
- 红利-您可以缓存PL / SQL逻辑,但此处禁止这样做。
如何启用?方法1
指定result_cache语句非常简单。 幻灯片显示结果标识符已出现。 因此,第一次执行查询时,数据库将做一些工作;在随后的执行过程中,在这种情况下,不需要任何工作。 一切都很好。
方法2

第二种方法允许应用程序开发人员不执行任何操作-这些就是所谓的注释。 我们为该表指示一个选中标记,该标记应将对它的请求放置在result_cache中。 因此,没有任何提示,我们不触摸应用程序,所有内容都已经在result_cache中。
顺便说一句,如果查询引用了两个表,其中一个被标记为result_cache,而第二个没有标记,您会如何看待这种查询的结果呢?
答案是否定的,根本没有。
为了对其进行缓存,参与查询的所有表都必须具有result_cache注释。
依赖追踪
在相关视图中,您可以查看什么依赖项。

在上面的示例中,JOIN查询是某个表,其中存在一个依赖性。 怎么了 因为Oracle不仅通过解析来确定依赖关系,而且还
根据工作计划的结果来实现它。
在这种情况下,选择这种计划是因为仅使用了一个表,并且实际上通过外键约束将工作表链接到雇员表。 如果我们删除允许进行此联接消除转换的外键约束,则将看到两个依赖关系,因为计划将以这种方式进行更改。
Oracle不会跟踪不需要跟踪的内容 。
在PL / SQL中,依赖项在运行时运行,因此您可以使用动态SQL并执行其他操作。

请注意,您不仅可以缓存整个请求,
还可以通过和从缓存内联视图 。 假设我们需要一个缓存,而另一件事最好是从数据库中读取,以免造成压力。 我们采用内联视图,再次将其声明为result_cache,然后看到仅缓存了一部分,第二部分我们每次都访问数据库。

最后,
数据库也有封装 ,尽管没有人相信。 我们采用一个视图,将result_cache放入其中,我们的程序员甚至没有意识到它已被缓存。 下面我们看到实际上只有一部分有效。

残障人士
因此,让我们看一下Oracle什么时候会使result_cache无效。Published状态显示了缓存有效性的当前状态。 如我所说,当对result_cache的请求时,数据库中没有作业

当我们进行更新时,状态仍为已发布,因为尚未提交更新,其他会话应看到旧的result_cache。 这就是臭名昭著的阅读一致性。
但是在当前会话中,我们将看到负载已消失,因为在此会话中缓存被忽略了。 这是非常合理的,让我们提交-结果将变为无效,一切都将自行完成。

似乎-一个梦想! 依赖关系被认为是正确的-仅取决于请求。 但是,没有发现一些细微差别。
Oracle在许多不明显的情况下都会造成残疾 :
- 使用任何SELECT FOR UPDATE调用,依赖关系都会消失。
- 如果表具有未索引的外键,并且在标记为result_cache的表上进行了更新,这根本不影响任何事情,但是父表中发生了某些变化,则缓存也将变为无效。
- 这是最有趣的事情,它会尽可能地破坏生命-如果在标记为result_cache的表上进行了一些不成功的更新,则没有任何效果,但是在同一事务中,应用了其他任何更改,以某种方式影响了第一个表,因此无论如何result_cache将被重置。
关于result_cache仍然有一种反模式,当开发人员听说这是一件很酷的事情时,会想:“哦,有存储! 现在,我们将处理一些适用于2-3个分区的请求-在当前日期和上一个日期,将其标记为result_cache,它将始终从内存中获取!”
但是当事后改变父权制时,整个缓存就会失效,因为事实上result_cache中依赖项跟踪的单位始终是一个表,并且我不知道是否会有分区。
我们考虑并决定,我们将使用以下内容来制作推荐系统:
- 我们不会缓存所有的表,只会处理必要的表。
- 为长时间运行的查询设置result_cache。
我们检查了所有内容,进行了性能测试,
处理时间为30 s 。 一切都很好,开始生产!
脱衣服-睡觉了。 我们早上到达。 我们看到一封信:“识别至少需要20分钟,会话会冻结。” 他们为什么冻结?
30秒如何
变成20分钟 ?
他们开始了解,看一下数据库:
- 活动会话-400;
- 用于识别的文档中的平均行数-500;
- 最小列-5-8;
- 数据库中的会话数始终等于用户应用程序数乘以3! 而且result_cache不喜欢频繁访问它。
在进行内部调查之后,我们发现Java开发人员在3个线程中进行了识别。
我们很沮丧-5倍的负载,跌落,降级,即使有了这样的参数,也不会发生这样的沉陷。
显然,您需要了解。
监控方式

对于监视,我们有两个关键事项:
- V $ RESULT_CACHE_OBJECTS-所有对象的列表;
- V $ RESULT_CACHE_STATISTICS-整个result_cache的聚合统计信息。
MEMORY_REPORT是主题的变体,我们将不需要它们。
甲骨文是神奇的! 有很多文档,但是它是为那些从其他数据库切换而来的人设计的,以便他们阅读并认为Oracle非常酷! 但是
关于result_cache的所有信息仅与support有关 。

有一个细微之处在于,只要我们为了解决问题而转向这些对象,我们就会最终埋葬自己,从而加剧了这种情况! 在Oracle12.2于去年10月发布补丁之前,这些请求使result_cache无法访问状态和写入,直到完全计数为止。

因此,使用v $ result_cache_objects视图,我们发现缓存的对象列表中有成千上万的条目-比我们预期的要多得多。 此外,这些是我们在奇怪表上进行的某些查询中的对象-小型平板电脑和last_modified_date查询。 显然,
有人在我们的基础上设置了ETL 。
在向ETL开发人员发誓之前,我们检查了这些表是否启用了result_cache force选项,并记住我们自己打开了它,因为应用程序经常需要某些数据,并且缓存是适当的。

但是事实证明,
所有这些请求只是占用并清洗了我们的缓存 。 幸运的是,开发人员有机会影响生产中的ETL,因此我们能够更改result_cache以排除这些微小的请求。
您认为这更容易吗? -感觉不舒服! 缓存对象的数量减少,然后又上升到12,000。由于速度没有改变,我们继续研究了缓存的对象。

我们看起来-一堆请求,如此聪明,但都难以理解。 尽管使用Oracle 12的任何人都知道DS SVC是自适应统计信息。 需要提高性能,但是当有result_cache时,事实证明它会杀死他,因为竞争正在发生。 当然,这
只是出于支持目的 。
我们知道工作量的安排方式,并了解在我们的案例中,自适应统计不会特别从根本上改善我们的计划。 因此,我们英勇地关闭了它-结果在秘密手册中写成,每个文档10分钟。 不错,但还不够。
锁存器
result_cache和DS SVC之间的竞争是由于Oracle具有闩锁这一事实-轻巧的小锁。

在不详细介绍它们如何工作的情况下,我们尝试多次放置一个已命名的闩锁-未能成功-Oracle接过头睡着了
主题中的任何人都可以说,在result_cache中,两个块通过fetch放置在每个块上。 这些是细节。 result_cache中有两种闩锁:
1.在我们将数据写入result_cache期间锁存一段时间。

也就是说,如果您的请求已经工作了8 s,在这8 s的时间内,其他类似的请求(关键字“ same”)将无法执行任何操作,因为它们要等到将数据写入result_cache为止。 其他请求将被记录,但是它们将仅在第一行等待锁定。 他们将要等待多少时间是未知的;这是未记录的参数result_cache_timeout。 在那之后,他们开始忽略result_cache,并且工作缓慢。 但是,一旦从门上的最后一行的锁被释放,它们就会自动再次开始使用result_cache。
2.第二种类型的锁-也从第一行到最后一行从result_cache接收。
但是由于提取来自即时内存,因此它们会很快被删除。

请务必记住,当DBA看到数据库中的闩锁时,它会开始说:“闩锁! 等待时间-一切都消失了! » :
DBA, wait time , .

, ,
result_cache 10% .

. , , , . — Proper results are deleted.
. , — , , - .

support 2 , ,
result_cache . .
, . , , , workload 5 . , , .
?: . , .

4 :
- RESULT_CACHE_MAX_SIZE;
- RESULT_CACHE_MAX_RESULT;
- RESULT_CACHE_MODE;
- _RESULT_CACHE_MAX_TIMEOUT.

— . , 100 512, 6 .
, - . , Invalidation Count = 10000.
, . , job , . , . job , , .

, invalid , .
40 .
, . , , Oracle. !
SHELFLIVE — , read-consistent , 10 , . . , , .
—
SNAPSHOT . , , read-consistent — .
:

- — SYS.
- . , , Oracle , , . , Oracle , , 12.2 . , external - support, .
- sql pl/sql : current_date, current_time . , current_time, .
- .
- , CLOB, BLOB .
Result cache inside Oracle
Result_cache — Oracle Core. , , job result_cache (, hint, ) , APEX.

, Dynamic sampling , , , result_cache.

Oracle internals for result cache
result_cache:
- (storage) ;
- result_cache;
- result_cache shared pool.
:- .
- read-consistent.
- Result_cache, , .
:!
, . support Oracle, , 29 2017 .: Oracle E-Business suite result_cache, .

, , . support , , .

:
- - ;
- , , , , v$result_cache_memory dbms_result_cache.memory_report, .
, , , v_result_cache_objects .
, support note — support , .

, , : - . , , :
- hint result_cache;
- hint no result_cache;
- black_list, , , -.
?- , - , , ;
- 在引导期间禁用高速缓存,即快速断开连接,倾倒并打开。最好让系统稍慢一些,但先起作用,然后再出现故障。
正如我们所注意到的,服务器上缓存的主要问题是昂贵的服务器内存。Oracle有第三个最终解决方案。客户端结果缓存

上面显示了他的设备的示意图,这些是数据库和驱动程序的主要组件。
第一次访问客户端时,结果缓存将转到已预先配置的数据库,并从数据库接收客户端缓存的大小,并在第一次连接时一次性在客户端上安装此缓存。 缓存的查询首先访问数据库,然后将数据写入缓存。 其余线程请求共享驱动程序缓存,从而节省服务器内存和资源。 顺便说一下,有时取决于负载,驱动程序会将有关缓存使用情况的统计信息发送到数据库,然后可以查看该统计信息。
一个有趣的问题是,残疾如何发生?无效模式有两种,可以通过参数“ Invalidation lag”来增强。 这是Oracle允许驱动程序缓存不一致的程度。
当请求频繁进行且不会发生无效滞后时,使用第一种模式。 在这种情况下,流将转到数据库,更新缓存并从中读取数据。

如果Invalidation滞后失败,则除了查询结果之外,任何引用数据库的非缓存请求都将带来无效对象列表。 因此,它们在高速缓存中被标记为无效,并且所有操作均与第一种情况的图片相同。
在第二种情况下,如果经过的时间超过了Invalidation滞后时间,则客户端result_cache本身将转到数据库并说:“给我一份更改列表!” 也就是说,他本人保持了自己的状态。
配置客户端结果缓存非常简单 。 有2个选项:
- CLIENT_RESULT_CACHE_LAG —缓存滞后值;
- CLIENT_RESULT_CACHE_SIZE-大小(最小32 Kb,最大-2 GB)。

从应用程序开发人员的角度来看,客户端缓存与服务器缓存没有太大区别,他们还输入了提示result_cache。 如果是这样,那么它将开始被客户端使用-在.Net和Java上。

完成查询的10次迭代后,我得到了以下内容。

首先是创建,然后是9次缓存访问。 该表表明该内存也按块分配。 也要注意SELECT-它不是很直观。 老实说,在我开始处理这个问题之前,我什至不知道
GV$SESSION_CONNECT_INFO
的表示形式。 为什么Oracle没有将其直接带到该表(这是一个表,而不是视图),我无法理解。 但这就是为什么我认为此功能不是很流行的原因,尽管在我看来,它非常有用。
客户端缓存的优点:- 便宜的客户端内存;
- 任何可用的驱动程序-JDBC,.NET等;
- 对应用程序代码的影响最小。
- 减少CPU,I / O和一般数据库的负载;
- 无需学习和使用各种智能缓存层和API;
- 没有闩锁。
缺点:- 延迟阅读具有一致性-原则上,这是一种趋势;
- 需要Oracle OCI客户端;
- 每个客户端2 GB的限制,但是通常2 GB是很多;
- 对我个人而言,关键限制是有关生产的一些信息。
关于支持,在处理result_cache时我们经常使用该支持,我发现只有5个错误。 这表明,极有可能很少有人需要它。
因此,我们将上述所有内容汇总在一起。
手工缓存
不良情况:- 即时更改-如果更改数据后,缓存应立即变得无关紧要。 对于自制的缓存,如果要对其构建的对象进行更改,则很难创建正确的失效。
- 如果开发策略禁止使用存储在数据库中的逻辑。
好方案:- 有强大的数据库开发团队。
- 实现PL / SQL逻辑。
- 存在一些限制,无法使用其他缓存技术。
服务器端结果缓存
不良情况:- 洗了整个缓存的结果很多。
- 请求花费的时间超过_RESULT_CACHE_TIMEOUT或此参数配置不正确。
- 来自非常大的会话的结果将以并行线程的形式加载到缓存中。
好方案:- 合理数量的缓存结果。
- 相对较小的数据集(200-300行)。
- 非常昂贵的SQL,否则所有的时间都将用于闩锁。
- 或多或少的静态表。
- 有一个DBA,如果有的话,它将拯救所有人。
客户端结果缓存
不良情况:好方案:- 有一个正常的中间层开发团队。
- 许多SQL已经在使用,而没有使用可以轻松连接的外部缓存层。
- 腺体有限制。
结论
我相信我的故事是关于服务器端结果缓存的痛苦的,所以结论如下:
- 请始终考虑查询数量而不是结果数量来正确评估内存大小,即:块,APEX,作业,自适应统计信息等。
- 不要害怕使用自动缓存刷新选项(快照+保质期)。
- 加载大量数据时,请勿使请求超载高速缓存;在此之前禁用result_cache。 预热缓存。
- 确保_result_cache_timeout符合您的期望。
- 切勿对整个数据库使用FORCE。 需要内存中的数据库-使用专门的内存解决方案。
- 检查FORCE选项是否适当地用于单个表,以使其无法像我们使用第三方ETL那样工作。
- 确定自适应统计信息是否与Oracle描述的一样好(_optimizer_ads_use_result_cache = false)。
下周一, Highload ++ Siberia 已准备好时间表,并在网站上发布。 本文主题中有一些报告:
- Alexander Makarov (CFT GC) 将以 Oracle数据库为例, 演示一种识别软件服务器端瓶颈的方法。
- Ivan Sharov和Konstantin Poluektov将告诉您将产品迁移到新版本的Oracle数据库时会出现什么问题,并承诺为组织和进行此类工作提供建议 。
- Nikolay Golov 将告诉您如何确保微服务架构中的数据完整性,而无需分布式事务和紧密连接。
在新西伯利亚见我!