Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/83.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql 我的IF条件中的漏洞在哪里?_Sql_Oracle_If Statement_Plsql_Race Condition - Fatal编程技术网

Sql 我的IF条件中的漏洞在哪里?

Sql 我的IF条件中的漏洞在哪里?,sql,oracle,if-statement,plsql,race-condition,Sql,Oracle,If Statement,Plsql,Race Condition,Oracle数据库11g企业版11.2.0.3.0版-64位生产版 我最近参加了一个项目,我的主要工作是清除大量积压的缺陷。其中一个缺陷是不必要的记录重复。我已经追查到罪犯是 在对象的相应列上缺少唯一约束 桌子 PL/SQL中的一个难以捉摸的问题 执行插入操作的例程 业务部门可以接受在开发和测试环境dbs中实施建议的唯一约束。但由于各种原因,在生产数据库中强制执行唯一约束是不可接受的。因此,我还提出了一个解决方案,即引入两个助手例程,在插入点充当“大而结实的保镖”。其目的是让这些拟议的“守门人”

Oracle数据库11g企业版11.2.0.3.0版-64位生产版

我最近参加了一个项目,我的主要工作是清除大量积压的缺陷。其中一个缺陷是不必要的记录重复。我已经追查到罪犯是

  • 在对象的相应列上缺少唯一约束 桌子
  • PL/SQL中的一个难以捉摸的问题 执行插入操作的例程
  • 业务部门可以接受在开发和测试环境dbs中实施建议的唯一约束。但由于各种原因,在生产数据库中强制执行唯一约束是不可接受的。因此,我还提出了一个解决方案,即引入两个助手例程,在插入点充当“大而结实的保镖”。其目的是让这些拟议的“守门人”例程通过“记住”已经插入的内容,以编程方式防止重复,然后仅在具有当前id的记录无法在上述“内存”中解释时才允许插入

    我已经对这些程序进行了单元测试。我已经对它们进行了单独的单元测试(每个单独测试)。我已经用«防止重复()»调用«已经存在()»例程对它们进行了单元测试。这两个我已经在纯PL/SQL和Java()中进行了单元测试。我还用纯PL/SQL对原始的«遗留»例程进行了单元测试,该例程被重构为调用«防止重复()»,然后调用«已经存在()。在我的每一个单元测试中,所有例程都成功地完成了我所期望的

    但是,只有当从webapp远程调用例程时,duplicates才能通过«prevent_duplicates()»中的IF检查。我已经将stacktrace的一个片段粘贴在帖子的底部

    所以也许我的问题是围绕着我的期望。也许我离这个问题太近了,因此我可能会做出一些天真的假设,认为新鲜的眼睛(以及更具知识性的PL/SQL)可能一眼就能发现

    FUNCTION already_exists (
     p_main_thing IN lorem_ipsum.main_id%TYPE, -- NUMBER(10)
     p_type IN lorem_ipsum.entity_type%TYPE, -- VARCHAR(256) 
     p_location IN lorem_ipsum.another_id%TYPE, -- NUMBER(10)
     p_start IN lorem_ipsum.start_using%TYPE, -- DATE
     p_stop IN lorem_ipsum.stop_using%TYPE -- DATE NULLABLE
     ) RETURN NUMBER AS
      m_counter NUMBER := 0;
    BEGIN
    
    SELECT count(eg.pk_id) INTO m_counter
        FROM lorem_ipsum eg
        WHERE eg.main_id = p_main_thing
        AND eg.entity_type  = p_type
        AND eg.another_id  = p_location
        AND eg.start_using = p_start
        AND NVL(eg.stop_using, TRUNC(SYSDATE-1000000)) = NVL(p_stop, TRUNC(SYSDATE-    1000000));
        commit;
        IF m_counter > 0 THEN
          RETURN 1; -- TRUE
        ELSE
          RETURN 0; -- FALSE
        END IF; 
    
    END already_exists;
    
    
    ================================================================================
    PROCEDURE prevent_duplicates(
     p_main_thing IN lorem_ipsum.main_id%TYPE,
     p_type IN lorem_ipsum.entity_type%TYPE, 
     p_location IN lorem_ipsum.another_id%TYPE, 
     p_start IN lorem_ipsum.start_using%TYPE, 
     p_stop IN lorem_ipsum.stop_using%TYPE,
     p_new_pk_id OUT lorem_ipsum.pk_id%TYPE, -- NUMBER(10)
     p_memory IN OUT NOCOPY short_term_memory ) -- TYPE short_term_memory IS TABLE OF BOOLEAN INDEX BY PLS_INTEGER;
     IS
    
     m_new_pk_id lorem_ipsum.pk_id%TYPE;
    
     BEGIN
    
     IF ( already_exists(p_main_thing, p_type, p_location, p_start, p_stop ) = 0 ) THEN
       IF ( NOT p_memory.EXISTS( p_main_thing ) ) THEN
            m_new_pk_id := pk_id_seq.nextval; -- allowed in 11g ; but not in 10g or lower
            insert into lorem_ipsum (pk_id, entity_type, another_id, start_using,     stop_using, main_id) values (m_new_pk_id, p_type, p_location, p_start, p_stop,     p_main_thing);
            commit;
            p_memory(p_main_thing) := TRUE;
            -- return the new pk_id to the caller
            p_new_pk_id := m_new_pk_id;
      END IF;
     END IF;
    
    -- EXCEPTION
    -- ... trap ORA-00001/raise user-defined exception -20999
    
    END prevent_duplicates;
    
    ...
    org.hibernate.Session hibernate = ...
    ...
    hibernate.beginTransaction();
    String orginalLegacyRoutine = "{call myapp.original_legacy_routine("+parentId+",   666)}";
    hibernate.createSQLQuery(orginalLegacyRoutine).executeUpdate();
    hibernate.getTransaction().commit();
    ...
    hibernate.close
    ...
    
    …这些都是我在上述例行程序中编撰的一些假设

    • 我假设如果在同一个Oracle事务中进行选择 前一次插入是在几秒钟之前执行的 会成功地找到之前插入的 记录SELECT的WHERE子句和INSERT的 values子句完全相同
    • 我假设«防止重复() 已插入记录的ID被“记忆”的关联数组
    • 我想他们之间应该已经决定了发生了什么 已插入
    • 我假设插入不应该被调用两次 相同的值
    • 如果测试阻止这种情况,我假设«防止重复()。但事实并非如此
    • 我假设SELECT和INSERT都是在 与调用例程相同的事务,因为原始的«遗留»例程是在事务边界内从Java调用的(请参见上面的代码块)
    此外,我正在开发和测试环境dbs上复制复制缺陷,在其中,我是唯一一个通过调用web应用程序执行例程的用户。事实上,我一直在办公室里孤独地度过深夜和周末,试图弄明白这一点。因此,由于这一点以及我对Oracle承诺的读取一致性的假设,我看不出它与并发有什么关系。然而,我承认,我对甲骨文采取隔离级别的记忆有点模糊。但据我记忆所及,我认为我被掩护了

    所以我需要帮助找出我所做的错误假设。请先谢谢你

    p.S.我无法使用调试器单步执行PL/SQL,因为远程调试被禁用,因为安全策略和我正在工作的商店中没有生效的东西


    您已经测试并说服自己(据我所知,这是正确的),您所编写的内容只适用于一次会话。因此,问题一定来自同时会话的并发调用,正如您可能从使用多个连接的多线程web应用程序中得到的那样

    你逻辑上的漏洞是,如果从两个会话调用例程,你仍然有一个竞争条件;在支票和插入之间的一小段时间,例如:

    Session A                     Session B
    ----------------------------  ----------------------------
    calls prevent_duplicates()
                                  calls prevent_duplicates()
    calls already_exists()
    gets zero (false)
                                  calls already_exists()
                                  gets zero (false)
    checks p_memory.exists()
    gets false
                                  checks p_memory.exists()
                                  gets false
    performs insert
    commits
                                  performs insert
                                  gets constraint violation
    

    其他一些与你的问题没有直接关系的观察结果

    您的
    p_内存
    检查实际上并没有在此处添加任何内容,因为其内容无论如何都是特定于会话的;如果插入是在另一个会话中完成的,您将看不到它,并且由于插入将在放入集合时提交,因此即使是跨会话插入,也不会告诉您任何额外的信息

    似乎您正在尝试以静默方式阻止尝试插入重复项。如果允许您使用唯一的约束-我假设这是出于某种原因不允许在live中使用的约束,并且您显示的是用于查找漏洞的约束所在的开发/测试版本-您可以跳过已存在的
    p_内存
    检查,只需捕获并忽略(或记录)ORA-00001即可。还有一个关于“捕获并忽略”与“插入前检查”的好处的单独辩论,这更离题了

    但是如果没有唯一约束,您必须通过锁定整个表或其他所有会话都可以看到并尝试锁定的唯一令牌来手动序列化插入,这可能会影响性能。您将以一种效率较低的方式重新实现独特性

    即使离主题更远,您也无法更进一步地理解或修复
    Session A                     Session B
    ----------------------------  ----------------------------
    calls prevent_duplicates()
                                  calls prevent_duplicates()
    calls already_exists()
    gets zero (false)
                                  calls already_exists()
                                  gets zero (false)
    checks p_memory.exists()
    gets false
                                  checks p_memory.exists()
                                  gets false
    performs insert
    commits
                                  performs insert
                                  gets constraint violation