Oracle存储过程,返回ref游标与关联数组



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

存储过程的示例如下:

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开发人员)以这种方式编写存储过程吗?

DBA的请求没有意义。

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

在Oracle中,当PL/SQL VM向SQL VM请求更多数据时,就会发生SQL到PL/SQL的上下文转换,SQL VM会通过进一步执行该语句来获取数据,然后将数据打包并交回PL/SQL虚拟机。如果PL/SQL引擎一次只请求一行,而您获取了很多行,那么这些上下文转换可能是整个运行时的一个重要部分。为了解决这个问题,甲骨文至少在8年前就引入了批量运营的概念。这允许PL/SQL虚拟机一次从SQL虚拟机请求多行。如果PL/SQLVM一次请求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来缓冲结果,并且等待网络的时间更长。

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

然而,真正的问题是,为什么不直接问他理由呢。他可能指的是甲骨文可以利用的技巧,如果你了解细节,你可以充分利用速度技巧。一般来说,终极的"总是这样做,因为这样更快"是可疑的,应该仔细观察才能完全理解他们的意图。在某些情况下,这可能真的不适用(例如,小查询结果),所有的可读性问题和开销都对性能没有帮助。

也就是说,这样做可能是为了保持代码的一致性和更快的可识别性。沟通他的推理是最重要的工具,因为他很可能知道一个他没有完全阐明的商业秘密。

最新更新