在Oracle中使列READONLY的最简单方法是什么?

时间:2011-11-30 18:20:30

标签: sql oracle oracle11g

我们有一个奇怪的神秘数据损坏错误,每隔几周弹出一次,没有人知道原因。到目前为止,似乎表上的主键是自发更改的,因此指向它的其他行现在搞砸了。

虽然我仍然在寻找这个的根本原因(这是不可能的重复),但我想要某种临时的黑客来防止列值不断变化。这是表模式:

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,无论正在运行什么奇怪的查询。

我能想到的一些想法:

  • 我不太熟悉Oracle的表权限(我更多 一个Postgres家伙),但你不能授予或拒绝更新权利 所有用户都有一栏?这只会影响更新,或 INSERTS也是? DENY更新到此列的命令是什么?
  • 创建一些在ROW UPDATE上运行的触发器。我们可以吗 检测INITIATIVEID是否被更改,如果是,则抛出 异常或以某种方式爆炸?

至少,我们可以捕获和/或记录此事件以查看它何时发生以及导致INITIATIVEID更改的查询是什么?

谢谢!

3 个答案:

答案 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)

第二种选择可能更好。如果您有一个日志记录表/文件,那么每次尝试更改该值时,您都可以尝试使用尽可能多的诊断信息编写消息。