2012-03-09 87 views
1

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

的存储过程的一个例子是下面的:

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

回答

12

DBA的请求没有意义。

数据库管理员几乎可以肯定的想法是,他希望尽量减少从游标中获取数据时继续执行的SQL到PL/SQL引擎上下文转换的次数。但是,正在提出的解决方案针对这个特殊问题的目标很差,并且在大多数系统中引入了其他更严重的性能问题。

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

一旦引入批量操作,就会有很多代码可以重构,以便通过明确使用BULK COLLECT操作来提高效率,而不是逐行获取,然后使用FORALL循环来处理那些数据集合。到了10点。但是,2天后,Oracle已将批量操作集成到隐式的FOR循环中,因此隐式的FOR循环现在会自动批量收集100个批次,而不是逐行读取。

但是,就您而言,由于您要将数据返回给客户端应用程序,因此批量操作的使用不太重要。任何体面的客户端API都会具有功能,可以让客户端指定在每次网络往返中需要从光标中提取多少行,并且这些提取请求将直接转到SQL VM,而不是通过PL/SQL虚拟机,所以没有SQL来转换PL/SQL上下文的担心。您的应用程序必须担心在每次往返中获取适当数量的行 - 这足以使应用程序不会在网络上变得过于健谈和瓶颈,但又不会太多,以至于您不得不等待太久才能得到结果返回或将太多数据存储在内存中。

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

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

+0

谢谢。多么好,详细的答案!如果可以的话,我会给你两张票。 – zaq 2012-03-09 20:47:54

0

我相信Oracle会开始从这样的系统发送结果,因为它扫描数据库,而不是全部检索并将它们发送回来。这意味着结果会在发现时发送,从而加速系统运行。 (实际上,如果我没有记错,它会将结果以批量形式返回到循环中。)这主要来自某些训练的内存

然而真正的问题是为什么不直接向他询问他的推理。他可能指的是甲骨文可以利用的一个技巧,如果你了解具体细节,你可以利用速度技巧来充分发挥它的潜力。一般来说,最终的“总是这样做,因为这是更快”作为可疑,值得仔细看看,充分理解他们的意图。在某些情况下,这实际上并不适用(例如小的查询结果),其中所有的可读性问题和开销都无助于性能。

也就是说,它可以做到保持代码一致和更快速识别。就他的推理而言,交流是最重要的工具,因为他知道一个商业秘密,因为他没有充分说明问题,所以机会很大。

+0

我问了不止一次,我真正得到的唯一答案是,甲骨文的一些联系人告诉他这是事实,我的理解是,这是几年前。我只是希望有一位Oracle专家能够明确地告诉我他为什么对,或者他为什么错了。 – zaq 2012-03-09 19:43:41

+0

这种方法可以防止结果在扫描时返回。您可能正在考虑用流水线函数来消除返回结果之前处理整个游标的瓶颈。 – llayland 2012-03-15 04:13:52