首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >我的假设条件上的洞在哪里?

我的假设条件上的洞在哪里?
EN

Stack Overflow用户
提问于 2015-01-17 19:41:49
回答 1查看 189关注 0票数 3

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

我最近参加了一个项目,我的主要工作是清理大量积压的缺陷。这些缺陷之一是不必要的记录重复。我已经找到了罪犯是.

  1. 表中适当列的唯一约束的缩写。
  2. 在执行插入的一个遗留例程中,PL/SQL中的一个难以捉摸的http://bit.ly/HeisenbuggingMeToDeath

在开发和测试环境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)可能一眼就能发现.

代码语言:javascript
运行
复制
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是我在上述例程中所做的一些假设.

  • 我假设,如果在前面的插入执行的同一个Oracle事务中执行了SELECT,则SELECT将成功地命中以前的INSERTed记录--如果SELECT的WHERE子句和INSERT的values子句中的值完全相同的话。
  • 我假设«prevent_duplicates()两个IF语句与关联数组(其中已经插入的记录的IF被记忆)串通在一起。
  • 我假设他们之间应该决定什么已经是INSERTed了。
  • 我假设,对于相同的值,不应该调用两次INSERT。
  • 如果测试阻止了这种情况,我假设«prevent_duplicates()。但事实并非如此。
  • 我假设SELECT & INSERT与调用例程在同一个事务中执行,因为原始的遗留例程是在事务边界内从Java调用的(参见上面的代码块)

此外,我正在复制开发和测试环境dbs上的复制缺陷,在这个环境中,我是通过调用web应用程序执行例程的唯一用户。事实上,我一直在办公室里孤零零地在晚上和周末试图弄清楚这件事。因此,正因为如此--以及我对Oracle的阅读一致性承诺的假设--我看不出它是如何与并发相关的。不过,我承认,我对Oracle的隔离级别的记忆有点模糊。不过,据我记忆所及,我以为我已经被覆盖了。

所以我需要帮助找出我所做的不正确的假设。请?提前谢谢你。

P.S.我无法使用调试器通过PL/,因为远程调试是禁用的,这是因为安全策略以及在我工作的商店中生效的东西。

代码语言:javascript
运行
复制
...
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
...
EN

回答 1

Stack Overflow用户

发布于 2015-01-18 00:27:01

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

逻辑上的漏洞是,如果从两个会话调用该例程,则仍然存在一个竞赛状态;在一个很短的时间内,您可以在检查和插入之间切换,例如:

代码语言:javascript
运行
复制
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_existsp_memory检查,只需捕获并忽略(或记录) ORA-00001。有一个单独的辩论,关于捕获和忽略与检查前插入的好处,这是一个更偏离主题.

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

更离题的是,你并没有更好地理解或修复原始的Heisenbug,你只是用潜在的副作用来更好地处理它,或者用潜在的副作用忽略它。如果我明白你在做什么,那就是。你似乎在试图隐藏一个缺陷--正在插入重复的缺陷--而不是修复它。您正在尝试解决您未知的问题,这个问题本身可能与多个会话相关,使用的方法也会受到多个会话问题的影响--只是可能不太频繁。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/28003492

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档