Oracle - WITH子句的事务范围

时间:2016-07-19 18:47:49

标签: sql oracle stored-procedures transactions

考虑遵循Java / JUnit集成测试场景。

有一个当前时间数据库表,其中存储了当前处理日,并且集成测试正在更新它以模拟测试时间(我宁愿不讨论这是否是一个好的方法)。测试在一个事务中运行,一切都在最后回滚。

现在,在模拟时间之后,执行查询当前时间表的存储过程,并根据该表进行一些处理。

在那里多次查询时间,但特别是在 WITH 子句中,更新的模拟时间不会被读取,而是之前已经存在的旧模拟时间(例如测试前的状态)开始)。

在这种情况下,似乎不尊重事务边界。我的理论是,它与Oracle为 WITH 查询构建临时表这一事实有关,但我没有找到任何证据。

实际的SQL,其中的注释解释了正在发生的事情:

-- mocking of the test time
-- updates the CURRENT_RUN_DATE to the test time, the previous value was 2016-06-14 08:30:51
UPDATE CURRENT_RUN_DATE SET RUN_DATE = '2014-10-06 07:05:00';

-- SQL of the actual Stored Procedure

WITH SOME_TEMPORARY_VIEW
AS (
  SELECT
    *  
  FROM MY_DATA_TABLE d
  WHERE
-- function F_PREVIOUS_RUN_DATE just select the RUN_DATE from CURRENT_RUN_DATE,
-- yet the old value is read
    d.RUN_DATE = (SELECT F_PREVIOUS_RUN_DATE  FROM DUAL)
)

SELECT
-- here goes some more sql, not important
-- however, if the F_PREVIOUS_RUN_DATE is called here, 
-- it reads the correct RUN_DATE, e.g. the one that was set in the first step
 *
FROM SOME_TEMPORARY_VIEW mv;

使用的Oracle版本:Oracle Database 11g企业版版本11.2.0.3.0

更新

根据答案/评论我要添加更多细节。 在存储过程中,实际使用了两个WITH子句。 依赖关系是ACTUAL_SELECT_STATEMENT -> ANOTHER_VIEW -> SOME_TEMPORARY_VIEW

实际的SQL:

-- mocking of the test time
-- updates the CURRENT_RUN_DATE to the test time, the previous value was 2016-06-14 08:30:51
UPDATE CURRENT_RUN_DATE SET RUN_DATE = '2014-10-06 07:05:00';

-- here begins the problematic SQL
WITH SOME_TEMPORARY_VIEW
AS (
  SELECT
    *,
    (SELECT F_PREVIOUS_RUN_DATE FROM DUAL) as PREVIOUS_RUN_DATE_DEBUG
  FROM MY_DATA_TABLE stic
  WHERE
    -- F_PREVIOUS_RUN_DATE select the PREVIOUS_RUN_DATE from CURRENT_RUN_DATE
    -- the old incosistent value is read here
    stic.RUN_DATE = (SELECT F_PREVIOUS_RUN_DATE FROM DUAL)
),
    ANOTHER_VIEW
  AS (
      SELECT DISTINCT
      -- selects from the first view, does some calculations
        *
      FROM SOME_TEMPORARY_VIEW tv)
SELECT
mv.*,
-- F_PREVIOUS_RUN_DATE reads correct value here
(SELECT F_PREVIOUS_RUN_DATE FROM DUAL) AS PREVIOUS_RUN_DATE_DEBUG2
FROM ANOTHER_VIEW mv;

这是截图,证明读取了不一致的数据(参见DEBUG列): query_result

无论如何,感谢@ ibre5041提供有关提示的信息。

这是有趣的故事。

应用INLINE提示后,查询按预期工作。看起来问题在某种程度上与WITH和物化到临时表有关。

2 个答案:

答案 0 :(得分:1)

使用简单的SQL语句,Oracle会选择一个时间(或更确切地说是系统变更编号或SCN),并且所使用的数据在该时间点保持一致。

当您在查询中使用存储的函数(无论WHEN如何)时,它会被抛出窗口。优化器可以选择执行一次或多次该功能,并且在该功能内执行的任何查询都作为独立的时间点/ SCN运行。

您可以使用

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; 

使Oracle在整个事务期间使用相同的SCN。 http://docs.oracle.com/cd/B13789_01/server.101/b10743/consist.htm#i17846

答案 1 :(得分:1)

如果它像你描述的那样工作那么它就是一个bug。 WITH子句可以内联或具体化(请参阅INLINEMATERIALIZE提示)。使用这些提示并检查执行计划的更改方式。在这两种情况下,查询都必须访问读取一致数据。并且还必须返回相同的结果。

如果你调用pl / sql函数会发生这种情况,这些函数也会调用sql。然后这些函数可以看到幻像(新创建的数据) - 因为它们在不同的SCN上下文中运行。正如@Gary所描述的那样。