Oracle数据库11g企业版11.2.0.3.0版 - 64位生产
我最近加入了一个项目,我的主要工作是清除大量积压的缺陷。其中一个缺陷是不必要的记录重复。我追查了罪魁祸首......
企业可以在开发和测试环境dbs中强制执行建议的唯一约束。但由于各种原因,在生产数据库中强制执行唯一约束是不可接受的。正因为如此,我还提出了一个解决方案,即在INSERT点引入一些辅助例程,用作« Big Burly Bouncers »。意图是这些提议的«网守»例程通过«记住»以编程方式防止重复“已经INSERT的内容然后只允许INSERT,如果具有当前id的记录不能被计算因为在前面提到的«记忆»。
我已经对这些例程中的地狱进行了单元测试。我已经单独对它们进行了单元测试(每个单独测试)。我用« prevent_duplicates()»调用« already_exists()»例程对它们进行了单元测试。这两个我在纯PL / SQL和Java( using Spring's StoredProcedure abstraction )中进行了单元测试。我还在纯PL / SQL中进行了单元测试,原来的«遗产»例程重构为调用« prevent_duplicates()»,后者又调用« already_exists() »。在我的每个单元测试中,所有例程都成功地完成了我的期望。
但是,只有当从webapp远程调用例程时,复制才能通过« prevent_duplicates()»进行IF检查。我在帖子的底部粘贴了一个堆栈跟踪片段。
所以也许我的问题集中在我期待的事情上。也许我太接近这个问题了,因此我可能会做出一些天真的假设,那些新鲜的眼睛(和更多知识渊博的PL / SQLers )可能乍看之下......
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
...
......这些是我在上述例程中编纂的一些假设......
另外,我正在重现开发和测试环境dbs上的复制缺陷,其中我是通过调用Web应用程序执行例程的唯一用户。事实上,我在办公室里一直都是在晚上和周末都很寂寞,试图解决这个问题。因此,以及我对Oracle对READ一致性的承诺的假设 - 我无法看到它与并发相关的方式。但我承认,我对Oracle的隔离级别的记忆有点模糊。尽管如此,我还是认为我被覆盖了。
所以我需要帮助弄清楚我做了哪些不正确的假设。请?提前谢谢。
PS 我无法通过调试器逐步执行PL / SQL,因为安全策略禁用了远程调试,而且我正在工作的商店还有其他功能。< / em>的
...
INFO | jvm 15 | 2015/01/16 20:00:51 | Jan 16, 2015 8:00:51 PM org.apache.catalina.core.ApplicationContext log
INFO | jvm 15 | 2015/01/16 20:00:51 | SEVERE: Exception while dispatching incoming RPC call
INFO | jvm 15 | 2015/01/16 20:00:51 | com.google.gwt.user.server.rpc.UnexpectedException: Service method 'public abstract dba.stackexchange.com.questions.ask.MyApp dba.stackexchange.com.questions.ask.MyAppRPC.addLoremIpsum(dba.stackexchange.com.questions.ask.LoremIpsum,java.lang.String)' threw an unexpected exception: org.hibernate.QueryTimeoutException: could not execute native bulk manipulation query
INFO | jvm 15 | 2015/01/16 20:00:51 | at com.google.gwt.user.server.rpc.RPC.encodeResponseForFailure(RPC.java:389)
INFO | jvm 15 | 2015/01/16 20:00:51 | at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:579)
INFO | jvm 15 | 2015/01/16 20:00:51 | at com.google.gwt.user.server.rpc.RemoteServiceServlet.processCall(RemoteServiceServlet.java:208)
INFO | jvm 15 | 2015/01/16 20:00:51 | at com.google.gwt.user.server.rpc.RemoteServiceServlet.processPost(RemoteServiceServlet.java:248)
INFO | jvm 15 | 2015/01/16 20:00:51 | at com.google.gwt.user.server.rpc.AbstractRemoteServiceServlet.doPost(AbstractRemoteServiceServlet.java:62)
INFO | jvm 15 | 2015/01/16 20:00:51 | at javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
INFO | jvm 15 | 2015/01/16 20:00:51 | at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
INFO | jvm 15 | 2015/01/16 20:00:51 | at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
INFO | jvm 15 | 2015/01/16 20:00:51 | at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
INFO | jvm 15 | 2015/01/16 20:00:51 | at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
INFO | jvm 15 | 2015/01/16 20:00:51 | at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
INFO | jvm 15 | 2015/01/16 20:00:51 | at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
INFO | jvm 15 | 2015/01/16 20:00:51 | at com.googlecode.psiprobe.Tomcat60AgentValve.invoke(Tomcat60AgentValve.java:30)
INFO | jvm 15 | 2015/01/16 20:00:51 | at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
INFO | jvm 15 | 2015/01/16 20:00:51 | at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:555)
INFO | jvm 15 | 2015/01/16 20:00:51 | at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
INFO | jvm 15 | 2015/01/16 20:00:51 | at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
INFO | jvm 15 | 2015/01/16 20:00:51 | at org.apache.jk.server.JkCoyoteHandler.invoke(JkCoyoteHandler.java:190)
INFO | jvm 15 | 2015/01/16 20:00:51 | at org.apache.jk.common.HandlerRequest.invoke(HandlerRequest.java:291)
INFO | jvm 15 | 2015/01/16 20:00:51 | at org.apache.jk.common.ChannelSocket.invoke(ChannelSocket.java:769)
INFO | jvm 15 | 2015/01/16 20:00:51 | at org.apache.jk.common.ChannelSocket.processConnection(ChannelSocket.java:698)
INFO | jvm 15 | 2015/01/16 20:00:51 | at org.apache.jk.common.ChannelSocket$SocketConnection.runIt(ChannelSocket.java:891)
INFO | jvm 15 | 2015/01/16 20:00:51 | at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:690)
INFO | jvm 15 | 2015/01/16 20:00:51 | at java.lang.Thread.run(Thread.java:679)
INFO | jvm 15 | 2015/01/16 20:00:51 | Caused by: org.hibernate.QueryTimeoutException: could not execute native bulk manipulation query
INFO | jvm 15 | 2015/01/16 20:00:51 | at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:124)
INFO | jvm 15 | 2015/01/16 20:00:51 | at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
INFO | jvm 15 | 2015/01/16 20:00:51 | at org.hibernate.engine.query.NativeSQLQueryPlan.performExecuteUpdate(NativeSQLQueryPlan.java:219)
INFO | jvm 15 | 2015/01/16 20:00:51 | at org.hibernate.impl.SessionImpl.executeNativeUpdate(SessionImpl.java:1310)
INFO | jvm 15 | 2015/01/16 20:00:51 | at org.hibernate.impl.SQLQueryImpl.executeUpdate(SQLQueryImpl.java:396)
INFO | jvm 15 | 2015/01/16 20:00:51 | at dba.stackexchange.com.questions.ask.MyAppRPCImpl.addLoremIpsum(Unknown Source)
INFO | jvm 15 | 2015/01/16 20:00:51 | at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
INFO | jvm 15 | 2015/01/16 20:00:51 | at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
INFO | jvm 15 | 2015/01/16 20:00:51 | at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
INFO | jvm 15 | 2015/01/16 20:00:51 | at java.lang.reflect.Method.invoke(Method.java:616)
INFO | jvm 15 | 2015/01/16 20:00:51 | at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:561)
INFO | jvm 15 | 2015/01/16 20:00:51 | ... 22 more
INFO | jvm 15 | 2015/01/16 20:00:51 | Caused by: java.sql.SQLException: ORA-20999: An attempt to insert lorem_ipsum.pk_id: 47396 violated DEDUPE_UNIQUE constraint with: main_id := 6459 , entity_type := FOO, another_id := 858, start_using := 04-JUL-08, stop_using :=
INFO | jvm 15 | 2015/01/16 20:00:51 | ORA-06512: at "SCOTT.MYAPP", line 504
INFO | jvm 15 | 2015/01/16 20:00:51 | ORA-06512: at "SCOTT.MYAPP", line 741
INFO | jvm 15 | 2015/01/16 20:00:51 | ORA-06512: at "SCOTT.MYAPP", line 538
INFO | jvm 15 | 2015/01/16 20:00:51 | ORA-06512: at line 1
INFO | jvm 15 | 2015/01/16 20:00:51 |
INFO | jvm 15 | 2015/01/16 20:00:51 | at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:445)
INFO | jvm 15 | 2015/01/16 20:00:51 | at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:396)
INFO | jvm 15 | 2015/01/16 20:00:51 | at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:879)
INFO | jvm 15 | 2015/01/16 20:00:51 | at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:450)
INFO | jvm 15 | 2015/01/16 20:00:51 | at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:192)
INFO | jvm 15 | 2015/01/16 20:00:51 | at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:531)
INFO | jvm 15 | 2015/01/16 20:00:51 | at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:207)
INFO | jvm 15 | 2015/01/16 20:00:51 | at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:1044)
INFO | jvm 15 | 2015/01/16 20:00:51 | at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1329)
INFO | jvm 15 | 2015/01/16 20:00:51 | at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3584)
INFO | jvm 15 | 2015/01/16 20:00:51 | at oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(OraclePreparedStatement.java:3665)
INFO | jvm 15 | 2015/01/16 20:00:51 | at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeUpdate(OraclePreparedStatementWrapper.java:1352)
INFO | jvm 15 | 2015/01/16 20:00:51 | at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:105)
INFO | jvm 15 | 2015/01/16 20:00:51 | at org.hibernate.engine.query.NativeSQLQueryPlan.performExecuteUpdate(NativeSQLQueryPlan.java:210)
INFO | jvm 15 | 2015/01/16 20:00:51 | ... 30 more
...
答案 0 :(得分:4)
您已经测试并确信自己(正确地说,就我所知)您所撰写的内容适用于单个会话。因此,问题必须来自同步会话的并发调用,因为您可能会从使用多个连接的多线程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_memory
支票并未在此处添加任何内容,因为其内容无论如何都是特定于会话的;如果插入在另一个会话中完成,你将无法看到它,并且因为插入将在它被放入集合时提交,所以即使它是跨会话也不会告诉你任何额外的
您似乎试图默默地阻止尝试插入副本。如果您被允许使用唯一约束 - 并且我假设这是由于某种原因您不能被允许的那个,并且您所显示的是开发/测试版本,其中约束是找到漏洞 - 您可以跳过already_exists
和p_memory
检查,然后捕获并忽略(或记录)ORA-00001。关于catch-and-ignore与插入前检查的好处还有一个单独的争论,这个讨论更偏离主题......
但是如果没有唯一约束,则必须通过锁定整个表或者所有会话可以查看并尝试锁定的其他唯一令牌来手动序列化插入,这可能会影响性能。你将以一种效率较低的方式重新实现独特性。
即使是更偏离主题,你也不能更接近理解或修复原始的Heisenbug,你只是稍微处理它有潜在的副作用,或忽略潜在的副作用。如果我明白你在做什么,那就是。您似乎试图隐藏缺陷 - 正在插入重复项 - 而不是修复它。您尝试解决您的未知问题,这个问题本身可能与多会话相关,其方法也会受到多会话问题的影响 - 可能不那么频繁。