Oracle包返回空表的间歇性问题

时间:2012-02-03 19:56:19

标签: sql oracle oracle11g

我们这里有一个非常复杂的Oracle软件包(超过4,100行代码)给我们带来了问题。我的任务是追查问题。问题是,当我们调用execute_filter程序时,我们希望得到6个表。但是,每天大约10-15次我们的代码崩溃,因为表索引1超出范围。当这个重复,它似乎重复几次然后一分钟后,它再次运作良好。我还没有能够在调试器下重新调用它以确切地看到数据集是什么 - 但我有一个理论,数据集只是一个空表。

挖掘Oracle软件包几乎是不可能的,因为它是一个大型的运行查询,没有格式化,没有缩进,以及通过连接字符串和不连接来构建其他查询的页面和代码页。但是,我有一个关于发生了什么的理论。

execute_filter方法会调用其他几十种方法中的一种或多种,​​例如filter_by_areas_name。这些方法中的每一个都查询一些数据并将此数据插入名为tpm_temp_filter_project的表中。一个例子是:

FOR I IN 1..areaState.COUNT LOOP
   INSERT INTO tpm_temp_filter_project
   (
      projectid,
      versionid
   )
   SELECT .. --Grabs the data it needs from other tables

在每个过滤器调用结束时,我们调用一个名为populate_result_table的过程,该过程将tpm_temp_filter_project中的内容复制到另一个表中然后执行:

EXECUTE IMMEDIATE 'truncate table tpm_temp_filter_project';

所以,我的理论是,如果两个人同时运行这个查询,那么这些“holder”表中的行会过早地被截断,而另一个查询仍然需要它们。

阻止此类事件发生的最佳方法是什么?我有一个想法就是:

LOCK TABLE tpm_temp_filter_project IN EXCLUSIVE MODE;

execute_filter的最开始,最后一行是COMMIT;。理论上,这应该只允许一个人同时运行命令,待处理的请求将“阻塞”直到第一个过滤器完成。我还没试过,但我有几个问题。

  1. 这是一个关于发生了什么的好理论吗?
  2. 这是一个很好的解决方案,还是有更好的解决方案来解决这个问题?
  3. 我很欣赏对此问题的任何见解。

    更新

    这是临时表的架构:

    CREATE GLOBAL TEMPORARY TABLE TPMDBO.TPM_TEMP_FILTER_PROJECT  ( 
        PROJECTID   NUMBER NULL,
        VERSIONID   NUMBER NULL 
        )
    ON COMMIT DELETE ROWS
    

    另一个更新:

    这似乎不是两个会话之间的冲突。如果我改变:

    EXECUTE IMMEDIATE 'truncate table tpm_temp_filter_project';
    

    为:

    DELETE tpm_temp_filter_project;
    

    然后错误仍然发生。即使我完全注释掉那一行,错误仍然最终会发生。该包体中没有任何其他内容可以删除,截断或修改任何其他数据。

    第二条证据 - 我终于在Visual Studio调试器下重复了错误。 .NET中的DataSet完全为空。有一个名为 table 的表没有列。如果这是一个会话删除这些临时表中的数据的问题,那么我期望一个有效的模式,其中包含来自错误会话的零行或行。

5 个答案:

答案 0 :(得分:2)

truncate是DDL,因此它会自动发出提交,这就是您在会话中看到效果的原因。

最明显,最好的解决方案(如@Glenn的评论中提到的)是使用带有on commit delete rows的全局临时表。这将确保此表中的数据仅在创建它的事务期间存在。

如果您需要跨越事务,可以使用带有on commit preserve rows的全局临时表,但是您需要确保使用单个会话来访问该数据。如果您在处理结束时结束会话,则数据将自动删除。但是,如果您重新使用会话(即您使用会话池或某些此类会话池),那么您将需要在处理结束时删除整个表。

另一种解决方案是使用delete而不是truncatedelete将限制对当前会话的更改,直到发出commit为止。如果您在同一事务中insertdelete,则效果将与使用全局临时表大致相同。

您询问的解决方案,显式锁定整个表,也应该可以正常工作。但是,您可能会发现性能受此解决方案的影响,因为您将有效地序列化您的处理。其他解决方案没有此限制。


根据修订后的问题:

由于您已经在使用全局临时表(GTT),因此我的大多数答案都变得无关紧要。现在的问题似乎是“为什么GTT过早被清除”。由于您使用的是on commit delete rows,因此可能的答案是某些事情导致您的交易过早结束。寻找commitrollback嵌套在哪里只会偶尔执行。另一个明显的罪魁祸首可能是通过execute immediate执行DDL,例如重置序列。

如果找不到类似内容,可以尝试将GTT更改为on commit preserve rows。这将允许GTT中的数据在事务中持续存在,并且如果您的进程在完成时关闭它的会话,那么它应该仍然是安全的。

答案 1 :(得分:2)

问题最终是因为包状态被间歇性重置。经过几天的调试(因为问题只在生产服务器上重新编写),我终于找到了原因。

在代码中调用了一个过程,该过程将一些数据存储在局部变量中。之后,一些C#代码在内部运行,最后再次在数据库连接上调用Open()(即使连接已经打开)。而不是无操作,再次调用Open()似乎关闭并重新打开与数据库的连接 - 至少使用我们使用的Oracle驱动程序。在100次中有99次,它只是从连接池中选择相同的连接并继续正常工作。但是,每隔一段时间它就会选择一个不同的连接,我们的会话ID会发生变化,包状态会丢失。

评论Open()电话会立即解决问题。

答案 2 :(得分:1)

好的,基于您的更新,您有一个全局临时表(GTT)。

因为它是GTT,你应该截断它。将数据加载到其中,处理数据,完成后,只需执行提交即可。由于它被定义为“在提交删除行上”,因此不需要截断,并且从性能/可伸缩性的角度来看,提交也更好。

顺便说一下,由于它是GTT,因此数据的范围/可见性处于会话级别。也就是说,特定会话写入表格的任何内容都只能被该会话看到。

因此,您之前关于两个人同时查询的评论是不正确的。每个会话都会看到它自己独特的数据集。

希望有所帮助。

答案 3 :(得分:0)

如果这个临时表真的是一个特定于会话的缓冲区(并不是要在会话中共享),那么我认为使用collections(嵌套表和/或关联数组)将是更好的方法去,可能要快得多。

答案 4 :(得分:0)

那应该是好的。

无论如何,如果您找到一种方法来限制dml执行的数量以加载相同的数据而不涉及更多复杂性,那么您可以获得更好的结果。在我看来,这将是最好的事情。