Oracle数据库11g企业版发布11.2.0.3.0-64位生产
我最近参加了一个项目,我的主要工作是清理大量积压的缺陷。这些缺陷之一是不必要的记录重复。我已经找到了罪犯是.
在开发和测试环境dbs中强制执行提议的唯一约束是业务可以接受的。但是,由于各种原因,在生产数据库中强制执行唯一约束是不可接受的。正因为如此,我还提出了一种解决方案,在插入点引入几个助手例程,以充当“大块头保镖”。这样做的目的是为了让这些拟议的守门员例程以编程方式防止重复,方法是记住已经存在的INSERTed,然后只有在无法在上述内存中记录当前id的情况下才允许插入。
我已经测试过这些程序了。我已经对它们进行了隔离测试(每个单元都是独立的)。我用«prevent_duplicates()调用«already_exists()例程对它们进行了单元测试。我已经用纯PL/SQL和Java (http://bit.ly/SpringSprocs)对这两种语言进行了测试。我还在纯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
...
...These是我在上述例程中所做的一些假设.
此外,我正在复制开发和测试环境dbs上的复制缺陷,在这个环境中,我是通过调用web应用程序执行例程的唯一用户。事实上,我一直在办公室里孤零零地在晚上和周末试图弄清楚这件事。因此,正因为如此--以及我对Oracle的阅读一致性承诺的假设--我看不出它是如何与并发相关的。不过,我承认,我对Oracle的隔离级别的记忆有点模糊。不过,据我记忆所及,我以为我已经被覆盖了。
所以我需要帮助找出我所做的不正确的假设。请?提前谢谢你。
P.S.我无法使用调试器通过PL/,因为远程调试是禁用的,这是因为安全策略以及在我工作的商店中生效的东西。
...
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
...
发布于 2015-01-18 00:27:01
您已经测试并说服自己(据我所知是正确的),您编写的内容适用于单个会话。因此,问题必须来自来自同时会话的并发调用,就像您可能从使用多个连接的多线程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。有一个单独的辩论,关于捕获和忽略与检查前插入的好处,这是一个更偏离主题.
但是,如果没有唯一的约束,您必须通过锁定整个表来手动序列化插入,或者其他所有会话都可以看到并试图锁定的唯一令牌,这很可能会影响性能。您将以一种效率较低的方式重新实现唯一性。
更离题的是,你并没有更好地理解或修复原始的Heisenbug,你只是用潜在的副作用来更好地处理它,或者用潜在的副作用忽略它。如果我明白你在做什么,那就是。你似乎在试图隐藏一个缺陷--正在插入重复的缺陷--而不是修复它。您正在尝试解决您未知的问题,这个问题本身可能与多个会话相关,使用的方法也会受到多个会话问题的影响--只是可能不太频繁。
https://stackoverflow.com/questions/28003492
复制相似问题