【问题标题】:Oracle - transaction scope of the WITH clause [duplicate]Oracle - WITH 子句的事务范围 [重复]
【发布时间】:2016-07-19 18:47:48
【问题描述】:

考虑以下 Java/JUnit 集成测试场景。

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

现在,在模拟时间之后,会执行一个查询当前时间表的存储过程,并在此基础上进行一些处理。

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

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

用 cmets 解释发生了什么的实际 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 Enterprise Edition Release 11.2.0.3.0

更新

根据答案/cmets,我正在添加更多详细信息。 在存储过程中,实际使用了两个 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 列):

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

下面是有趣的故事。

应用INLINE 提示后,查询确实工作如预期。看起来问题与 WITH 和物化到临时表有关。

【问题讨论】:

    标签: sql oracle stored-procedures transactions


    【解决方案1】:

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

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

    你可以使用

    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; 
    

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

    【讨论】:

    • 谢谢,尝试将隔离级别设置为SERIALIZABLE,但结果仍然相同。无论如何,你可能想看看更新的帖子,我会说整个事情对我来说看起来像是一个错误。
    【解决方案2】:

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

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

    【讨论】:

    • 确实,使用INLINE,查询开始正常运行,我在帖子中添加了更多详细信息。
    • 如果提示改变了结果,那么你肯定会遇到 Oracle 错误。我记得我前段时间看到过类似的东西。要么盲目应用最新的 PSU,要么联系 Oracle 支持。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-18
    • 1970-01-01
    • 2020-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-09
    相关资源
    最近更新 更多