考虑遵循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列):
无论如何,感谢@ ibre5041提供有关提示的信息。
这是有趣的故事。
应用INLINE
提示后,查询按预期工作。看起来问题在某种程度上与WITH和物化到临时表有关。
答案 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
子句可以内联或具体化(请参阅INLINE
和MATERIALIZE
提示)。使用这些提示并检查执行计划的更改方式。在这两种情况下,查询都必须访问读取一致数据。并且还必须返回相同的结果。
如果你调用pl / sql函数会发生这种情况,这些函数也会调用sql。然后这些函数可以看到幻像(新创建的数据) - 因为它们在不同的SCN上下文中运行。正如@Gary所描述的那样。