【问题标题】:What is the easiest way to make a column READONLY in Oracle?在 Oracle 中使列只读的最简单方法是什么?
【发布时间】:2023-03-30 09:35:01
【问题描述】:

我们有一个奇怪的神秘数据损坏错误,每隔几周就会出现一次,没有人知道为什么。到目前为止,表上的主键似乎在自发地改变,所以指向它的其他行现在都乱了。

虽然我仍在寻找导致此问题的根本原因(无法重现),但我想要某种临时破解来防止列值发生变化。这是表架构:

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 的人),但你不能授予或拒绝更新权限 某些列给所有用户?这只会影响更新,还是 也插入? DENY 更新此列的命令是什么?
  • 创建某种在 ROW UPDATE 上运行的触发器。我们可以吗 检测 INITIATIVEID 是否被更改,如果是,则抛出 异常或以某种方式爆炸?

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

谢谢!

【问题讨论】:

  • 还要注意创建重复行和删除旧行。
  • 是的,我实际上注意到INITIATIVEID 上有一个 UNIQUE 约束,但有人禁用了它(?!?!) - 我已经开始重新启用它并验证它有效,所以希望这也能帮助这个错误消失。不过,我认为阻止对密钥的更新将解决这个问题。
  • 哦,顺便说一句,插入新行的代码非常糟糕。它基本上会执行“SELECT MAX(INITIATIVEID) FROM INITIATIVES”并在其中添加 1 以获得新 ID。这完全不是交易安全的,而且真的很慢。这就是为什么我喜欢使用 UUID 作为密钥的原因,但现在这将是一个相当大的变化。
  • @MikeChristensen 您也许可以在user_source 中查询SELECT MAX(INITIATIVEID) 并用序列替换它们..
  • 是的,使用序列是可行的方法 - 但是,Oracle 不支持自动为表使用序列,我不知道如何让 .NET Entity Framework 使用序列插入。

标签: sql oracle oracle11g


【解决方案1】:

如果子表填充了引用INITIATIVEID 列的数据,Oracle 应该通过阻止您通过更改父主键来创建孤立行来自动使更改主键值变得困难。因此,例如,如果有一个子表具有对TPM_INITIATIVES 的外键约束,并且该子表中有一行 INITIATIVEID 为 17,则您将无法更改 INITIATIVEID TPM_INITIAITVES 表中当前值为 17 的行的值。如果任何子表中没有引用 TPM_INITIATIVES 表中特定行的行,则可以更改该值,但大概,如果没有关系,更改主键值并不重要,因为根据定义,它不会导致数据完整性问题。当然,您可以使用新的INITIATIVEID 将新行插入TPM_INITIATIVES 的代码,将子表中引用旧行的所有行更改为引用新行,然后修改旧行。但这不会被任何建议的解决方案所困。

如果您的应用程序已定义子表但未声明适当的外键约束,那将是解决问题的最佳方法。

话虽如此,Arnon 的创建视图的解决方案应该可行。您将重命名该表,创建一个与现有表同名的视图,并(可能)在视图上定义一个 INSTEAD OF 触发器,该触发器根本不会更新 INITIATIVEID 列。这应该不需要更改应用程序的其他部分。

你也可以在表上定义一个触发器

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 中的某些内容可能有用)。

【讨论】:

  • 你的意思是如果另一个表在INITIATIVEID 上有一个FK 约束,那么Oracle 将不允许该键更改?我将检查这些约束是否存在,如果不存在则添加它们。我确实喜欢触发的想法,它看起来又快又容易。我同意 View 的想法将是最好的长期解决方案,但我对在生产服务器上实时应用这种更改感到非常紧张。我想我可以先在其中一个登台服务器上对其进行测试。
  • @Mike Christensen - 我扩展了外键约束位。 Oracle 不允许您通过更改父行的键来孤立子行。
  • 哦,是的。我不相信这些行是孤立的,但我们看到的是一个子行将指向主 A,然后突然它会指向主 B(它与主 A 具有相同的键),并且大师 A 现在神秘地消失了。
  • @MikeChristensen - 那你为什么认为问题在于主键正在改变?有人在更新行中的非关键数据以将其从 A 更改为 B 似乎不是更有可能吗?
  • 这是另一种可能性。我查看了更新这些记录的代码,一切看起来都很好。它通常不会发生,所以这是一些竞争条件或间歇性的事情。这就是为什么我现在的目标是尽可能多地消除可能性,也许根本原因最终会浮出水面。
【解决方案2】:

将表格隐藏在视图后面并使更新触发器更新除您要保护的列之外的所有内容

【讨论】:

  • 不幸的是,这种更改太混乱了,不会被批准用于生产数据库。我们需要一个快速的热修复。
  • 为什么? - 你更改表 重命名为 ,然后在旧名称上创建视图,这样所有内容对应用程序的其余部分都是透明的
  • 那我如何处理UPDATEs、DELETEs 和INSERTs 的视图呢?所有应用程序代码都针对旧表名使用原始 SQL。
  • 您使视图可更新,并为视图创建 INSTEAD OF 触发器以更新除您不想要的字段之外的所有字段。
  • 好的,有道理。谢谢!
【解决方案3】:

第二个选项可能更好。如果您有日志记录表/文件,则可以尝试在每次尝试更改值时写入包含尽可能多的诊断信息的消息。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-12-30
    • 2010-10-06
    • 1970-01-01
    • 2020-12-13
    • 2015-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多