我们这里有一个非常复杂的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;
。理论上,这应该只允许一个人同时运行命令,待处理的请求将“阻塞”直到第一个过滤器完成。我还没试过,但我有几个问题。
我很欣赏对此问题的任何见解。
更新
这是临时表的架构:
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 的表没有列。如果这是一个会话删除这些临时表中的数据的问题,那么我期望一个有效的模式,其中包含来自错误会话的零行或行。
答案 0 :(得分:2)
truncate
是DDL,因此它会自动发出提交,这就是您在会话中看到效果的原因。
最明显,最好的解决方案(如@Glenn的评论中提到的)是使用带有on commit delete rows
的全局临时表。这将确保此表中的数据仅在创建它的事务期间存在。
如果您需要跨越事务,可以使用带有on commit preserve rows
的全局临时表,但是您需要确保使用单个会话来访问该数据。如果您在处理结束时结束会话,则数据将自动删除。但是,如果您重新使用会话(即您使用会话池或某些此类会话池),那么您将需要在处理结束时删除整个表。
另一种解决方案是使用delete
而不是truncate
。 delete
将限制对当前会话的更改,直到发出commit
为止。如果您在同一事务中insert
和delete
,则效果将与使用全局临时表大致相同。
您询问的解决方案,显式锁定整个表,也应该可以正常工作。但是,您可能会发现性能受此解决方案的影响,因为您将有效地序列化您的处理。其他解决方案没有此限制。
根据修订后的问题:
由于您已经在使用全局临时表(GTT),因此我的大多数答案都变得无关紧要。现在的问题似乎是“为什么GTT过早被清除”。由于您使用的是on commit delete rows
,因此可能的答案是某些事情导致您的交易过早结束。寻找commit
或rollback
嵌套在哪里只会偶尔执行。另一个明显的罪魁祸首可能是通过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执行的数量以加载相同的数据而不涉及更多复杂性,那么您可以获得更好的结果。在我看来,这将是最好的事情。