Sql 我的IF条件中的漏洞在哪里?
Oracle数据库11g企业版11.2.0.3.0版-64位生产版 我最近参加了一个项目,我的主要工作是清除大量积压的缺陷。其中一个缺陷是不必要的记录重复。我已经追查到罪犯是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中实施建议的唯一约束。但由于各种原因,在生产数据库中强制执行唯一约束是不可接受的。因此,我还提出了一个解决方案,即引入两个助手例程,在插入点充当“大而结实的保镖”。其目的是让这些拟议的“守门人”
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调用的(请参见上面的代码块)
您已经测试并说服自己(据我所知,这是正确的),您所编写的内容只适用于一次会话。因此,问题一定来自同时会话的并发调用,正如您可能从使用多个连接的多线程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