2012-08-06 53 views
7

最近我遇到了一个奇怪的问题,在Oracle数据库中编程:在可序列化的事务中,我做了一个批量插入(INSERT ... SELECT),之后立即在改变的表格上用SELECT键打开游标。我认为这个游标会包含新插入的行,但令我惊讶的是,它的内容不稳定,有时包括所有新插入的行,有时只包含一个子集。Oracle:在可序列化事务中插入后立即选择

我已经通过在打开游标之前提交来解决了这个问题,但行为让我感到困惑。在同一个事务中插入一个insert后,如果没有间接提交,实际上可以被信任吗?或者,这种行为是否与可序列化的事务相关?我试图创建一个可重复的测试用例时,只有在添加索引(本例中为主键索引,在实际代码中为普通索引)时才能够获得此行为。也许问题在于构建索引所花费的时间,以便SELECT实际上使用不完整的索引来检索结果?无论如何,这里有一个可重复的测试用例:

-- Create empty source table 
CREATE TABLE TEST_CASE_1 AS 
    (SELECT 'CONTENT' AS CONTENT 
    FROM DUAL 
    WHERE 1 = 2) 

-- Add primary key 
ALTER TABLE TEST_CASE_1 
ADD CONSTRAINT TEST_CASE_1_PK PRIMARY KEY (CONTENT); 

-- Create empty destination table 
CREATE TABLE TEST_CASE_2 AS 
    (SELECT 'CONTENT' AS CONTENT 
    FROM DUAL 
    WHERE 1 = 2) 

-- Example of faulty code 
BEGIN 

    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; 

    -- Populate with 100.000 rows (I used ALL_OBJECTS but any source of 100.000 rows is good) 
    INSERT INTO TEST_CASE_1 
    (SELECT ROWNUM 
    FROM ALL_OBJECTS 
    WHERE ROWNUM <= 100000); 

    INSERT INTO TEST_CASE_2 
    (SELECT * 
    FROM TEST_CASE_1 
    WHERE CONTENT > 0); 

    COMMIT; 

END; 

在这个例子中,我希望TEST_CASE_2也有100.000行。重新生成这个测试用例(在一个无负载的数据库中),我获得了约400-500行插入。除去将事务设置为可序列化的语句,我获得了正确的100.000行计数。

+0

您是否在插入操作时在相同的数据库会话中打开游标?你在哪里做这项工作 - 实际上是在数据库还是从客户端应用程序 - 如果后者你有连接池,并且你(有时)为这两个操作获得不同的连接? – 2012-08-06 11:17:16

+0

游标正在同一会话中打开。最初游标在数据库中打开,然后在.NET可执行文件中迭代,但为了隔离问题,我制作了一个在数据库中迭代游标的过程版本,问题依然存在 - 事实上,在执行此操作之前,不要说服自己,问题出现在插入后的选择中。 – user1578874 2012-08-06 11:37:41

+0

这不应该发生;来自[文档](http://docs.oracle.com/cd/E11882_01/server.112/e25789/consist.htm#sthref1189)'在序列化隔离级别,事务仅查看在事务时提交的更改 - 不是查询 - 由交易本身开始并进行更改“,因此您应该看到自己的更改。我猜可能是你的特定版本中的一个错误。承诺使得改变隔离级别有点毫无意义。你能发布一个可重复的测试用例吗? – 2012-08-06 17:20:04

回答

8

这似乎是一个错误;如果您有权访问Oracle的支持网站,请参阅注释1455175.1,其历史可以追溯到8i。列出了一些错误编号(7592038-'隐藏数据来自选择/更新新插入的行在SERIALIZABLE',6363019),但它们被关闭为440317的重复项('隔离级别可序列化的原因未找到所选行的数据后插入“),尽管它最初是针对版本7(!)提出的,但仍显示为仍然开放并正在通过开发进行调查。

你似乎是对的,这是它与PK有关。列出的解决方法是:

  • 将执行的工作提交到该点。
  • 执行额外(但不同)的语句(可能在回滚到事务中早期建立的保存点之后)。
  • 回滚整个事务并从头开始重新启动事务。
  • 执行全表扫描并避免使用索引。

您知道第一个解决方法已经生效了,而且我不认为第二个或第三个解决方案可以帮助您吗?您可以尝试第四个,为第二个插入的选择添加一个/*+ FULL(TEST_CASE_1) */提示。

我没有在11.2.0.2(Linux)中看到错误,虽然我找不到任何暗示bug已经修复的错误;并且我没有11.1环境来尝试它 - 所以我无法检查最后的解决方法是否适用于此测试用例。

有一个说明,你可以在11G中获得ORA-08177。如果我在创建表之后过早运行匿名块,或者插入的行太多,这似乎也与PK有关,那么我遇到了这个问题。 This previous question可能是相关的。看起来像这样会继续是一个问题,所以如果变通方法无法帮助您,如果您确实需要更改隔离级别,则可能需要重新考虑;如果你这样做,你可能不得不向Oracle提出服务请求以获得更好的答案。

+0

非常感谢您的详尽解答!我遇到的错误似乎与Oracle支持中的问题相对应。我很高兴这是一个bug,因为我们依赖于序列化事务处理系统中非常小但任务关键的部分,如果这些是可序列化事务的标准语义,我们可能不得不重写整个部分。第一个解决方法(我使用的)在我的特定情况下很好,但在该程序的其他部分中没有问题。我将在我的工具箱中保留所有的解决方法。 – user1578874 2012-08-07 14:02:25

+1

看来,这个bug *仍然*是不固定的;我刚刚在Windows上使用Oracle 12.1.0.1.0进行了复制。 – 2014-12-03 22:25:35

+1

刚刚在Oracle 12.1.0.2.0 - 64位Linux上获得... – Jakob 2015-01-13 15:39:26

2

这是一个确认的错误,Oracle表示他们不打算修复它。下面是从他们对我的服务请求响应(2015年1月)的摘录:

这些症状是由于序列化交易被发现与 的已知问题和你的结论与错误440317是正确的。

错误440317 - ISOLATION LEVEL SERIALIZABLE原因FOUND上选择AFTER INSERT
错误16803610个不进行任何数据 - INSERTED使用INSERT INTO ROWS迷失在串行化隔离级别TRANSAC

这两种错误是发布,所以你可以看到在MOS 错误搜索中的细节。

根据开发,与 有很长的历史相同的问题有多个错误。设计不容易改变,因此没有修复这个功能,直到找到这个功能并不是非常有用。

开发已经关闭了错误说代码修复不可行。

提出的解决方法是
应用程序代码modifiction:
变化的逻辑面前选择
提交或不使用序列化
如果没有应用程序代码的修改:
不要使用主键或索引在桌面上

相关问题