我们有一个奇怪的神秘数据损坏错误,每隔几周弹出一次,没有人知道原因。到目前为止,似乎表上的主键是自发更改的,因此指向它的其他行现在搞砸了。
虽然我仍然在寻找这个的根本原因(这是不可能的重复),但我想要某种临时的黑客来防止列值不断变化。这是表模式:
CREATE TABLE TPM_INITIATIVES (
INITIATIVEID NUMBER NOT NULL,
NAME VARCHAR2(100) NOT NULL,
ACTIVE CHAR(1) NULL,
SORTORDER NUMBER NULL,
SHORTNAME VARCHAR2(100) NULL,
PROJECTTYPEID NUMBER NOT NULL,
CONSTRAINT TPM_INITIATIVES_PK PRIMARY KEY(INITIATIVEID)
NOT DEFERRABLE
VALIDATE
)
我们当然需要能够创建新行,但我想阻止ANYTHING永远改变INITIATIVEID
,无论正在运行什么奇怪的查询。
我能想到的一些想法:
INITIATIVEID
是否被更改,如果是,则抛出
异常或以某种方式爆炸?至少,我们可以捕获和/或记录此事件以查看它何时发生以及导致INITIATIVEID
更改的查询是什么?
谢谢!
答案 0 :(得分:6)
如果存在填充了引用INITIATIVEID
列的数据的子表,则Oracle应该通过更改父级主键来阻止您创建孤立行,从而自动使更改主键值变得困难。因此,例如,如果存在具有TPM_INITIATIVES
外键约束的子表,并且此子表中有一行INITIATIVEID
为17,则您将无法更改INITIATIVEID
表中当前值为17的行的TPM_INITIAITVES
。如果任何子表中没有引用TPM_INITIATIVES
表中特定行的行,则可以更改值,但可能是,如果没有关系,更改主键值是不重要的,因为根据定义,它不能导致数据完整性问题。当然,您可以使用新TPM_INITIATIVES
将新行插入INITIATIVEID
的代码,更改子表中引用旧行的所有行以引用新行,然后修改旧排。但这不会被任何提出的解决方案所困。
如果您的应用程序已定义子表但未声明适当的外键约束,那么这将是解决问题的最佳方法。
话虽这么说,Arnon创建视图的解决方案应该有效。您将重命名该表,创建一个与现有表同名的视图,并(可能)在视图上定义一个永远不会更新INITIATIVEID
列的INSTEAD OF触发器。这不应该要求更改应用程序的其他位。
您还可以在表格上定义触发器
CREATE TRIGGER trigger_name
BEFORE UPDATE ON TPM_INITIATIVES
FOR EACH ROW
DECLARE
BEGIN
IF( :new.initiativeID != :old.initiativeID )
THEN
RAISE_APPLICATION_ERROR( -20001, 'Sorry Charlie. You can''t update the initiativeID column' );
END IF;
END;
当然,有人可以禁用触发器并发出更新。但我假设你并没有试图阻止攻击者,只是一个有缺陷的代码。
根据您所看到的症状的描述,将此更改的历史记录记录到此表中的列似乎更有意义,以便您可以实际确定正在进行的操作而不是猜测并尝试一个接一个地插上孔。所以,例如,你可以做这样的事情
CREATE TABLE TPM_INITIATIVES_HIST (
INITIATIVEID NUMBER NOT NULL,
NAME VARCHAR2(100) NOT NULL,
ACTIVE CHAR(1) NULL,
SORTORDER NUMBER NULL,
SHORTNAME VARCHAR2(100) NULL,
PROJECTTYPEID NUMBER NOT NULL,
OPERATIONTYPE VARCHAR2(1) NOT NULL,
CHANGEUSERNAME VARCHAR2(30),
CHANGEDATE DATE,
COMMENT VARCHAR2(4000)
);
CREATE TRIGGER trigger_name
BEFORE INSERT or UPDATE or DELETE ON TPM_INITIATIVES
FOR EACH ROW
DECLARE
l_comment VARCHAR2(4000);
BEGIN
IF( inserting )
THEN
INSERT INTO tpm_initiatives_hist( INITIATIVEID, NAME, ACTIVE, SORTORDER, SHORTNAME, PROJECTTYPEID,
OPERATIONTYPE, CHANGEUSERNAME, CHANGEDATE )
VALUES( :new.initiativeID, :new.name, :new.active, :new.sortOrder, :new.shortName, :new.projectTypeID,
'I', USER, SYSDATE );
ELSIF( inserting )
THEN
IF( :new.initiativeID != :old.initiativeID )
THEN
l_comment := 'Initiative ID changed from ' || :old.initiativeID || ' to ' || :new.initiativeID;
END IF;
INSERT INTO tpm_initiatives_hist( INITIATIVEID, NAME, ACTIVE, SORTORDER, SHORTNAME, PROJECTTYPEID,
OPERATIONTYPE, CHANGEUSERNAME, CHANGEDATE, COMMENT )
VALUES( :new.initiativeID, :new.name, :new.active, :new.sortOrder, :new.shortName, :new.projectTypeID,
'U', USER, SYSDATE, l_comment );
ELSIF( deleting )
THEN
INSERT INTO tpm_initiatives_hist( INITIATIVEID, NAME, ACTIVE, SORTORDER, SHORTNAME, PROJECTTYPEID,
OPERATIONTYPE, CHANGEUSERNAME, CHANGEDATE )
VALUES( :old.initiativeID, :old.name, :old.active, :old.sortOrder, :old.shortName, :old.projectTypeID,
'D', USER, SYSDATE );
END IF;
END;
然后,您可以查询TPM_INITIATIVES_HIST
以查看随时间对特定行所做的所有更改。因此,您可以查看主键值是否正在更改,或者某人是否只是更改非键字段。理想情况下,您可以添加其他列以添加到历史记录表中以帮助跟踪更改(即,V$SESSION
中可能存在可能有用的内容)。
答案 1 :(得分:5)
隐藏视图后面的表并使更新触发器更新除要保护的列之外的所有内容
答案 2 :(得分:2)
第二种选择可能更好。如果您有一个日志记录表/文件,那么每次尝试更改该值时,您都可以尝试使用尽可能多的诊断信息编写消息。