【问题标题】:Oracle stored procedure, returning ref cursor vs associative arraysOracle 存储过程,返回 ref 游标与关联数组
【发布时间】:2012-03-27 05:36:42
【问题描述】:

我们的 DBA 要求我们从一组关联数组中的存储过程中返回所有表格数据,而不是使用我在网络上大多数示例中看到的引用游标。他说这是因为 Oracle 以这种方式做事要快得多,但对我来说这似乎违反直觉,因为数据需要循环两次,一次在存储过程中,然后在处理时再次在应用程序中。此外,通常需要将值从其本机类型转换为 varchar,以便将它们存储在数组中,然后在应用程序端进行转换。使用这种方法也使得使用 orm 工具变得困难,因为它们似乎在大多数情况下都需要引用游标。

一个存储过程的例子如下:

PROCEDURE sample_procedure (
                                p_One       OUT varchar_array_type,
                                p_Two       OUT varchar_array_type,
                                p_Three     OUT varchar_array_type,
                                p_Four      OUT varchar_array_type
                            )
IS
p_title_procedure_name        VARCHAR2(100) := 'sample_procedure';
v_start_time DATE :=SYSDATE;    

CURSOR cur
  IS
    SELECT e.one, e.two, e.three, e.four FROM package.table 
    WHERE filter='something';

    v_counter PLS_INTEGER := 0;
BEGIN

    FOR rec IN cur LOOP
        BEGIN
            v_counter := v_counter + 1;
            p_One(v_counter) := rec.one;
            p_Two(v_counter) := rec.two;
            p_Three(v_counter) := rec.three;
            p_Four(v_counter) := rec.four;
        END;
    END LOOP;
END;

光标用于为返回的每一列填充一个数组。我试图找到支持他的说法的信息,即这是一种更快的方法,但一直无法做到。谁能告诉我他为什么希望我们(.net 开发人员)以这种方式编写存储过程?

【问题讨论】:

    标签: arrays oracle plsql oracle11g


    【解决方案1】:

    DBA 的请求没有意义。

    几乎可以肯定,DBA 的想法是,他希望最大限度地减少从游标获取数据时发生的 SQL 到 PL/SQL 引擎上下文转换的次数。但是所建议的解决方案并没有很好地针对这个特定问题,并且在大多数系统中引入了其他更严重的性能问题。

    在 Oracle 中,当 PL/SQL VM 向 SQL VM 请求更多数据时,会发生 SQL 到 PL/SQL 的上下文转换,SQL VM 通过进一步执行语句来响应以获取数据,然后打包并返回到 PL/SQL 虚拟机。如果 PL/SQL 引擎一次请求一行,而您要获取很多行,那么这些上下文转换可能会占整个运行时的很大一部分。为了解决这个问题,Oracle 至少在 8i 时代就引入了批量操作的概念。这允许 PL/SQL VM 一次从 SQL VM 请求多行。如果 PL/SQL VM 一次请求 100 行,那么您已经消除了 99% 的上下文转换,并且您的代码可能运行得更快。

    引入批量操作后,可以通过显式使用BULK COLLECT 操作而不是逐行获取然后使用FORALL 循环来处理大量代码,从而提高效率。这些集合中的数据。然而,到了 10.2 天,Oracle 已将批量操作集成到隐式 FOR 循环中,因此隐式 FOR 循环现在会自动批量收集 100 个,而不是逐行获取。

    但是,在您的情况下,由于您将数据返回到客户端应用程序,因此批量操作的使用意义不大。任何体面的客户端 API 都将具有允许客户端指定在每次网络往返中需要从游标中获取多少行的功能,并且这些获取请求将直接发送到 SQL VM,而不是通过 PL /SQL VM,因此无需担心 SQL 到 PL/SQL 上下文的转换。您的应用程序必须担心在每次往返中获取适当数量的行 - 足以使应用程序不会变得太健谈和网络瓶颈,但也不会太多以至于您必须等待太长时间才能得到结果返回或在内存中存储太多数据。

    将 PL/SQL 集合而不是 REF CURSOR 返回给客户端应用程序不会减少发生的上下文转换次数。但它会有很多其他的缺点,其中最重要的是内存使用。 PL/SQL 集合必须完全存储在数据库服务器上的进程全局区域 (PGA)(假设专用服务器连接)中。这是一块必须从服务器的 RAM 中分配的内存。这意味着服务器将不得不分配内存来获取每个客户端请求的每一行。反过来,这将极大地限制应用程序的可扩展性,并且根据数据库配置,可能会从 Oracle 数据库的其他部分窃取 RAM,这对于提高应用程序性能非常有用。如果您的 PGA 空间用完,您的会话将开始出现与内存相关的错误。即使在纯粹基于 PL/SQL 的应用程序中,您也永远不会希望将所有数据提取到集合中,您总是希望以较小的批次提取数据,以最大限度地减少您使用的 PGA 数量。

    此外,将所有数据提取到内存中会使应用程序感觉慢得多。几乎任何框架都将允许您根据需要获取数据,例如,如果您有一份报告,每页显示 25 行,那么您的应用程序只需要在绘制前 25 行第一个屏幕。除非用户碰巧请求下一页结果,否则它永远不必获取接下来的 25 行。但是,如果您像 DBA 建议的那样将数据提取到数组中,那么您将必须在应用程序开始显示第一行之前提取所有行,即使用户不想看到的只是前几个行。这将意味着数据库服务器上有更多的 I/O 来获取所有行,服务器上有更多的 PGA,应用程序服务器上有更多的 RAM 来缓冲结果,并且等待网络的时间更长。

    【讨论】:

    • 谢谢。多么棒的,详细的答案!如果可以的话,我会给你两票。
    【解决方案2】:

    我相信 Oracle 会在扫描数据库时开始从这样的系统发送结果,而不是检索所有结果然后发回。这意味着结果会在找到时发送,从而加快系统速度。 (实际上,如果我没记错的话,它会将结果批量返回到循环中。)这主要来自一些训练的记忆

    然而,真正的问题是,为什么不直接问他的推理。他可能指的是甲骨文可以利用的技巧,如果您了解具体细节,您可以充分利用速度技巧。通常,“总是这样做,因为这样更快”的最终结果是可疑的,值得仔细研究以充分了解他们的意图。在某些情况下,这确实不适用(例如小查询结果),所有可读性问题和开销都无助于提高性能。

    也就是说,这样做可以使代码保持一致并更快地识别。就他的推理进行交流是解决此类问题的最重要工具,因为他很有可能知道一个他没有完全阐明的商业秘密。

    【讨论】:

    • 我已经问过不止一次了,我真正得到的唯一答案是甲骨文的一些联系人告诉他情况就是这样,我的理解是这是几年前的事了。我只是希望有一位 Oracle 大师可以明确地告诉我他为什么是对的或为什么他是错的。
    • 这种方法会阻止结果在扫描时返回。您可能正在考虑使用流水线函数来消除在返回结果之前处理整个游标的瓶颈。
    猜你喜欢
    • 2011-01-31
    • 2011-01-24
    • 1970-01-01
    • 2011-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-22
    相关资源
    最近更新 更多