0

在尝试在我们的一个系统中提高 SP 的可维护性时,我决定使用循环比硬编码一组值(在这种情况下为表名)更好,并尝试相应地重构代码以便添加或将表删除到系统不需要编辑数组。暂时撇开循环的原因和原因(我非常清楚反对它们的论点),任何人都可以解释发生了什么吗?

想象一下,两个用户 SourceUser 和 DestUser 都在同一个数据库中,每个用户的表都在同一个表空间中。SourceUser 中的一堆存储过程将 SourceUser 中的数据填充到 DestUser 以用于报告目的。作为其中的一部分,要运行的第一个过程会删除 DestUser 中的所有表并重新创建它们。同样,这里不讨论这样做的相对优点。

SourceUser 对 DestUser 具有 Drop Any Table 和 Create Any Table 权限。我们要保留 DestUser 中的一张表。所以,我在过程中构造的 SQL 如下所示:

Begin
  For T In (SELECT TABLE_NAME FROM all_tables WHERE TABLE_NAME != 'MIDBLOG' AND OWNER = sTarget_DB) Loop
    Begin
      Execute Immediate('Drop Table ' || sTarget_DB || '.' || T.TABLE_NAME);

    Exception
      When Others Then
        --Don't care if we get an exception here as most likely the table wasn't there to be dropped in the first place.
        NULL;
    End;
  End Loop;
End;

在这种情况下,sTarget_DB 设置为 DestUser,并且此代码针对 SourceUser 运行。

当程序运行时,我发现没有表被删除(我在开始之前确认有几十个表,包括一个名为 MIDBLOG 的表)。我在 SQL Developer 调试模式下运行它,执行甚至从未进入循环内部,因为它似乎认为它没有要处理的行,但我确定 select 语句将返回几十个表名。

接下来我将其修改为:

Begin
  For T In (SELECT TABLE_NAME FROM all_tables WHERE OWNER = sTarget_DB) Loop
    Begin
      If T.TABLE_NAME != 'MIDBLOG' THEN
        Execute Immediate('Drop Table ' || sTarget_DB || '.' || T.TABLE_NAME);
      End If;
    Exception
      When Others Then
        --Don't care if we get an exception here as most likely the table wasn't there to be dropped in the first place.
        NULL;
    End;
  End Loop;
End;

运行这个之后,唯一被删除的表就是我不想删除的那个!更奇怪的是循环只执行了一次,就好像选择查询只返回一行一样。如果我在 SQL Developer 3.2 的调试中运行该过程,我可以看到它正在发生。我们在同事的 PC 上使用 SQL Developer(可能是 3.1)做了同样的事情,循环只执行了一次,但这次它正确地决定不删除表 MIDBLOG 并再次留下其他所有内容。

如果我在 SQL Developer 中将上述示例中的任何一个作为匿名块运行,它就会完全符合我的预期。我尝试了更详细的显式游标声明,结果与以前相同。我从来没有得到任何例外。

所有这些都在 Oracle 10g Enterprise Edition Release 10.2.0.4.0(64 位)上。只要在 Oracle 11g Enterprise Edition Release 11.2.0.1.0 (64bit) 上尝试过,它就可以正常工作。为什么这样一个基本要求会在两个版本中表现出如此截然不同的行为?它可以在两个版本中使用相同的代码按我想要的方式工作吗?

4

1 回答 1

2

我的猜测是问题与权限有关,而不是 Oracle 的版本。权限DestUser是通过一个数据库中的角色授予还是SrcUser通过另一个数据库中的直接授予?

在运行匿名 PL/SQL 块之前,如果先禁用角色会发生什么?

set role none;
<<run the anonymous PL/SQL block>>

If you add instrumentation to the code, does the query against all_tables return the set of tables that you expect? My guess is that when the code fails, it is in a definer's rights stored procedure where the owner of the procedure has access to the DestUser tables via a role. Since privileges granted through a role are not visible in a definer's rights stored procedure, this would cause the SELECT statement in your loop to return 0 rows (though running the same query interactively would return the rows you expect). If the privileges on the DestUser tables are granted directly, on the other hand, the same definer's rights stored procedure would run successfully. And it would work in an anonymous PL/SQL block.

于 2012-11-29T19:12:46.953 回答