3

编辑:查看这个问题的结尾,了解导致错误的原因以及我是如何发现的。

当我运行一个将数据批量插入到 Oracle 数据库中的应用程序时,我从 Hibernate 中抛出了一个非常奇怪的异常。错误来自 Oracle 数据库ORA-00001,它

" 表示已尝试插入具有重复(唯一)键的记录。如果更新现有记录以生成重复(唯一)键,也会生成此错误。

该错误很奇怪,因为我在另一台机器上创建了相同的表(完全相同的定义),如果我通过我的应用程序使用它,我不会得到相同的错误。并且所有数据都被插入到数据库中,所以没有什么被真正拒绝。

两种设置之间必须有一些不同,但我能看到的唯一不同的是我在发布时得到的横幅输出

select * from v$version where banner like 'Oracle%';

给我带来麻烦的数据库: Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - Prod
有效的数据库: Oracle Database 10g Release 10.2.0.3.0 - 64bit Production

表定义、输入和我编写的应用程序对两者都是相同的。所涉及的表基本上是一个具有复合 id(serviceid、date、value1、value2)的四列表 - 没什么特别的。

关于什么可能是错的任何想法?我已经开始清理几次,删除两个表以平等地开始,但我仍然从数据库中得到错误。

更多的输出:

Caused by: java.sql.BatchUpdateException: ORA-00001: unique constraint (STATISTICS.PRIMARY_KEY_CONSTRAINT) violated

    at oracle.jdbc.driver.DatabaseError.throwBatchUpdateException(DatabaseError.java:367)
    at oracle.jdbc.driver.OraclePreparedStatement.executeBatch(OraclePreparedStatement.java:8728)
    at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70)

我如何找出导致问题的原因

感谢 APC 和 ik_zelf,我能够查明这个错误的根本原因。事实证明,Quartz 调度程序被错误地配置为生产数据库(出现错误的地方)。对于针对非故障 oracle 服务器<cronTriggerExpression>0/5 * * * * ?</cronTriggerExpression>运行的作业,我每五秒运行一次批处理作业。我认为对于另一个 oracle 服务器来说,每分钟一次就足够了,并使用 * */1 * * * ? 设置了石英调度程序。事实证明这是错误的,而不是每分钟运行一次,而是每秒运行一次!

每个作业大约需要 1.5-2 秒,因此两个或多个作业同时运行,从而导致服务器上同时插入。因此,我没有插入 529 个元素,而是插入了 1000 到 2000 个元素。将 crontrigger 表达式更改为与另一个相同的表达式,每五秒运行一次,解决了该问题。

为了找出问题所在,我必须在 hibernate.cfg.xml 中设置 true 并禁用表上的主键约束。

-- To catch exceptions
-- to find the offending rows run the following query
-- SELECT * FROM uptime_statistics, EXCEPTIONS WHERE MY_TABLE.rowid = EXCEPTIONS.row_id;
create table exceptions(row_id rowid,
                        owner varchar2(30),
                        table_name varchar2(30),
                        constraint varchar2(30));

-- This table was set up
CREATE TABLE MY_TABLE
  (
    LOGDATE DATE NOT NULL,
    SERVICEID           VARCHAR2(255 CHAR) NOT NULL,
    PROP_A   NUMBER(10,0),
    PROP_B NUMBER(10,0),
    CONSTRAINT PK_CONSTRAINT PRIMARY KEY (LOGDATE, SERVICEID)
  );

-- Removed the constraint to see what was inserted twice or more
alter table my_table
  disable constraint PK_CONSTRAINT;

-- Enable this later on to find rows that offend the constraints
alter table my_table
  enable constraint PK_CONSTRAINT
    exceptions into exceptions;
4

3 回答 3

4

你有一个独特的复合约束。ORA-00001 表示您有两行或多行在 ServiceID、Date、Value1 和/或 Value2 中有重复值。您说两个数据库的输入相同。所以要么:

  • 你想象你的程序正在抛出 ORA-00001
  • 您误认为两次运行中的输入相同。

更可能的解释是第二种:您的一个或多个关键列由外部源或默认值填充(例如,ServiceId 的代码表或日期列的 SYSDATE)。在您失败的数据库中,此自动填充无法提供唯一值。这可能有多种原因,具体取决于您使用的机制。请记住,在唯一的复合键中,NULL 条目计数。也就是说,您可以拥有任意数量的记录 (NULL,NULL.NULL,NULL),但只有一条记录 (42,NULL,NULL,NULL)。

我们很难猜测实际问题可能是什么,对您来说几乎同样困难(尽管您确实具有成为代码作者的优势,这应该会给您一些洞察力)。您需要的是一些跟踪语句。我首选的解决方案是使用批量 DML 异常处理,但我是 PL/SQL 粉丝。Hibernate 允许你在你的程序中加入一些日志记录:我建议你打开它。当代码具有良好的工具时,它更容易调试。

作为最后的手段,在运行批量插入之前禁用约束。然后像这样重新启用它:

alter table t42
    enable constraint t42_uk
        exceptions into my_exceptions
/

如果您有重复的行,这将失败,但至关重要的是 MY_EXCEPTIONS 表将列出所有冲突的行。这至少会给你一些关于重复来源的线索。如果您还没有异常表,则必须运行脚本:($ORACLE_HOME/rdbms/admin/utlexcptn.sql您可能需要 DBA 才能访问此目录)。


tl;博士

洞察力需要信息:检测您的代码。

于 2011-05-17T11:50:29.310 回答
1

有问题的是一个EE,另一个看起来像一个SE数据库。我希望第一个是在更快的硬件上。如果是这种情况,并且您的日期列是使用 SYSDATE 填充的,那么很可能是时间分辨率不够;你得到重复的日期值。如果数据的其他列也不是唯一的,则会得到 ORA-00001。

这是一个长镜头,但乍一看,我会朝这个方向看。

您可以使用异常表来识别数据吗?请参阅报告约束异常

于 2011-05-16T17:32:15.457 回答
1

我的猜测是服务ID。无论 service_id hibernate 用于“新鲜”插入,都已被使用。

可能该表在一个数据库中为空,但在另一个数据库中填充

我打赌虽然 service_id 是序列生成的,并且序列号与数据内容不同步。所以你在表中有相同的 1000 行但是做

SELECT service_id_seq.nextval FROM DUAL

在一个数据库中给出的数字低于另一个。我经常看到这个序列已经创建(例如,在源代码控制之外)并且数据已经从另一个数据库导入到表中。

于 2011-05-17T03:43:39.970 回答