强制删除Oracle全局临时表

17

在我们的项目中,我创建了一些类似以下的全局临时表:

CREATE GLOBAL TEMPORARY TABLE v2dtemp (
  id           NUMBER,
  GOOD_TYPE_GROUP       VARCHAR2(250 BYTE),
  GOOD_CODE             VARCHAR2(50 BYTE),
  GOOD_TITLE            VARCHAR2(250 BYTE)
)
ON COMMIT PRESERVE ROWS;

但是问题出现在我想删除这个表时。 Oracle不允许我删除这个表,它会显示:

ORA-14452: attempt to create, alter or drop an index on temporary table already in use

我必须在某个流程中使用这个表,但它可能会根据其他报告而改变。因此,我应该总是删除表,然后重新创建带有我需要的字段。

出于业务原因,我必须使用临时表,无法使用常规表或其他对象。我尝试了提交时删除行,但当我调用我的过程来使用此表中的数据时,表中就没有行,它们已被删除。

非常感谢任何帮助,提前致谢。

///编辑

public void saveJSONBatchOpenJobs(final JSONArray array, MtdReport report) {
    dropAndCreateTable();
    String sql = "INSERT INTO v2d_temp " +
            "(ID, KARPARDAZ, GOOD_TYPE_GROUP, GOOD_CODE, GOOD_TITLE, COUNT, "
            + "FACTOR_COUNT, GHABZ_COUNT, DEAL_NO, DEAL_DATE, REQUEST_NO, REQUEST_DATE, "
            + "REQUEST_CLIENT, STATUS, TYPE, MTDREPORT_ID, GEN_SECURITY_DATA_ID) " +
            "VALUES (MTD_KARPARDAZ_OPEN_JOBS_SEQ.nextval,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";

    getJdbcTemplate().batchUpdate(sql, new BatchPreparedStatementSetter() {

        @Override
        public void setValues(PreparedStatement ps, int i) throws SQLException {
            JSONArray values = array.getJSONArray(i);
            if(!values.get(0).equals("null"))
                ps.setString(1, values.get(0).toString());
            else
                ps.setNull(1, Types.VARCHAR);
            if(!values.get(1).equals("null"))
                ps.setString(2, values.get(1).toString());
            else
                ps.setNull(2, Types.VARCHAR);
            if(!values.get(2).equals("null"))
                ps.setString(3, values.get(2).toString());
            else
                ps.setNull(3, Types.VARCHAR);
            if(!values.get(3).equals("null"))
                ps.setString(4, values.get(3).toString());
            else
                ps.setNull(4, Types.VARCHAR);
            if(!values.get(4).equals("null"))
                ps.setBigDecimal(5, new BigDecimal(values.get(4).toString()));
            else
                ps.setNull(5, Types.NUMERIC);
            if(!values.get(5).equals("null"))
                ps.setBigDecimal(6, new BigDecimal(values.get(5).toString()));
            else
                ps.setNull(6, Types.NUMERIC);
            if(!values.get(6).equals("null"))
                ps.setBigDecimal(7, new BigDecimal(values.get(6).toString()));
            else
                ps.setNull(7, Types.NUMERIC);
            if(!values.get(7).equals("null"))
                ps.setString(8, values.get(7).toString());
            else
                ps.setNull(8, Types.VARCHAR);
            if(!values.get(8).equals("null"))
                ps.setDate(9, new Date(new Timestamp(values.getLong(8)).getDateTime()));
            else
                ps.setNull(9, Types.DATE);
            if(!values.get(9).equals("null"))
                ps.setString(10, values.get(9).toString());
            else
                ps.setNull(10, Types.VARCHAR);
            if(!values.get(10).equals("null"))
                ps.setDate(11, new Date(new Timestamp(values.getLong(8)).getDateTime()));
            else
                ps.setNull(11, Types.DATE);
            if(!values.get(11).equals("null"))
                ps.setString(12, values.get(11).toString());
            else
                ps.setNull(12, Types.VARCHAR);
            if(!values.get(12).equals("null"))
                ps.setString(13, values.get(12).toString());
            else
                ps.setNull(13, Types.VARCHAR);
            if(!values.get(13).equals("null"))
                ps.setString(14, values.get(13).toString());
            else
                ps.setNull(14, Types.VARCHAR);
            if(!values.get(14).equals("null"))
                ps.setLong(15, new Long(values.get(14).toString()));
            else
                ps.setNull(15, Types.NUMERIC);
            if(!values.get(15).equals("null"))
                ps.setLong(16, new Long(values.get(15).toString()));
            else
                ps.setNull(16, Types.NUMERIC);
        }

        @Override
        public int getBatchSize() {
            return array.size();
        }
    });

    String bulkInsert = "declare "
            + "type array is table of d2v_temp%rowtype;"
            + "t1 array;"
            + "begin "
            + "select * bulk collect into t1 from d2v_temp;"
            + "forall i in t1.first..t1.last "
            + "insert into vertical_design values t1(i);"
            + "end;";
    executeSQL(bulkInsert);
}

private void dropAndCreateTable() {
    String dropSql = "declare c int;"
            + "begin "
            + "select count(*) into c from user_tables where table_name = upper('v2d_temp');"
            + "if c = 1 then "
            + "truncate table v2d_temp"
            + "drop table v2d_temp;"
            + " end if;"
            + "end;";
    executeSQL(dropSql);

    String createSql = "CREATE GLOBAL TEMPORARY TABLE v2d_temp (\n"
            + "DEAL_ID               NUMBER,\n"
            + "id           NUMBER,\n"
            + "karpardaz  VARCHAR2(350),\n"
            + "GOOD_TYPE_GROUP       VARCHAR2(250 BYTE),\n"
            + "GOOD_CODE             VARCHAR2(50 BYTE),\n"
            + "GOOD_TITLE            VARCHAR2(250 BYTE),\n"
            + "COUNT                 NUMBER,\n"
            + "FACTOR_COUNT          NUMBER,\n"
            + "GHABZ_COUNT           NUMBER,\n"
            + "DEAL_NO               VARCHAR2(50 BYTE),\n"
            + "DEAL_DATE             DATE,\n"
            + "REQUEST_NO            VARCHAR2(50 BYTE),\n"
            + "REQUEST_DATE          DATE,\n"
            + "REQUEST_CLIENT        VARCHAR2(250 BYTE),\n"
            + "STATUS                VARCHAR2(250 BYTE),\n"
            + "TYPE                  VARCHAR2(250 BYTE),\n"
            + "GEN_SECURITY_DATA_ID  NUMBER(10),\n"
            + "MTDREPORT_ID          NUMBER\n"
            + ")\n"
            + "ON COMMIT PRESERVE ROWS";
    executeSQL(createSql);
}

private void executeSQL(String sql) {
    Connection con = null;
    try {
        con = getConnection();
        Statement st = con.createStatement();
        st.execute(sql);
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        if(con != null) {
            try {
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

8
全局临时表不应该被这样使用。它们应该作为模式的一部分定义,并且它们的结构不应该在每次使用/程序执行时更改。如果您有多个程序使用临时存储,请为每个所需数据结构定义全局临时表。您还可以在查询中使用子查询因子,它可以在后台创建临时表,或使用/*+ materialize */提示强制创建(有一些限制)。 - Husqvik
1
我的老板坚持让我使用这种方式,包括临时表等等,所以我别无选择。这已经是第二天了,我一直在寻找其他使用临时表并将数据用于其他部分的方法,你能指导我其他的方法吗? - Naeem Baghi
7
那么你的老板不理解 Oracle 的工作原理。 - user330315
2
找另一份工作吧,那里你不会学到任何有价值的东西。 - David Aldridge
2
也许你是对的,但现在,在这个时刻我必须找到解决这个问题的方法,他坚持认为有一种方法可以做到这一点,当我更详细地解释临时表无法做到这一点时,我会让他更生气,并认为我现在对oracle 一无所知!我在第一个答案的评论中解释了更多细节,如果你有任何关于如何强制oracle删除一些临时表而不考虑其他会话数据的想法,如果你与我分享,我将非常感激。 - Naeem Baghi
一次又一次地,我看到类似于这个问题的提问,答案通常是“Oracle 不是用来那种方式使用的”,然后...嘭...10 年后 Oracle 推出了那个确切的功能。这不是一个有帮助的评论,只是对 OracleDB 工程设计的抱怨。 - Rade_303
4个回答

33

Oracle全局临时表不是瞬态对象,它们是适当的堆表。我们创建它们一次,任何会话都可以使用它们来存储仅对该会话可见的数据。

临时的方面在于数据不会持续超过一个事务或一个会话。关键的实现细节是将数据写入临时表空间而不是永久表空间。然而,数据仍然被写入和从磁盘读取,因此使用全局临时表会有明显的开销。

重点是我们不应该删除并重新创建临时表。如果您试图将SQL Server风格的逻辑移植到Oracle,则应考虑使用PL/SQL集合在内存中维护临时数据。了解更多信息。

ORA-14452的具体原因是,如果全局临时表在会话期间包含数据,则无法删除具有会话范围持久性的表。即使该表当前为空...

SQL> create global temporary table gtt23 (col1 number)
  2  on commit preserve rows
  3  /

Table created.

SQL> insert into gtt23 values (1);

1 row created.

SQL> commit;

Commit complete.

SQL> delete from gtt23;

1 row deleted.

SQL> commit;

Commit complete.

SQL> drop table gtt23;
drop table gtt23
           *
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table already in use

SQL>
解决办法是结束会话并重新连接,或者(有点奇怪地)截断表格然后删除它。

解决办法是结束会话并重新连接,或者(有点奇怪地)截断表格然后删除它。

SQL> truncate table gtt23;

Table truncated.

SQL> drop table gtt23;

Table dropped.

SQL> 

如果其他会话正在使用全局临时表(这是可能的,因此使用了“全局”命名法),那么在所有会话断开连接之前,您将无法删除该表。因此,真正的解决方案是学会正确地使用全局临时表:为每个报告创建特定的全局临时表,或者像我说的那样,使用PL/SQL集合。或者,甚至只需要学会编写良好调优的SQL。通常,我们使用临时表作为解决糟糕查询的临时方法,而这些查询可以通过更好的访问路径进行优化。


经过查看您的全部代码后,流程看起来更加奇怪:

  1. 删除和重新创建全局临时表
  2. 填充临时表
  3. 从临时表选择并插入到PL/SQL数组中
  4. 使用PL/SQL数组进行批量插入实际表中

这里有很多额外的开销和浪费的活动。您所需要做的就是获取插入到 v2d_temp 中的数据,并直接填充 vertical_design,最好使用INSERT INTO ... SELECT * FROM语句。您需要一些预处理步骤将JSON数组转换为查询,但Java或PL/SQL都很容易实现。

对于您的场景,我认为全局临时表不是正确的解决方案。


"我们的老板或其他人坚持以他们自己的方式做某事,因此你不能改变那个"

您面临的是一个老板问题而不是一个编程问题。因此,从StackOverflow的角度来看,它与主题无关。但是这里仍然有一些建议。

最重要的是要记住,我们讨论的不是在次优架构上进行妥协:您的老板提出的显然在多用户环境中不起作用。因此,您有以下几个选项:

  1. 忽略ORA-14452错误,进入生产环境,然后在一切都发生了可怕的情况下使用“但是您告诉我”的辩护。这是最弱的策略。
  2. 秘密废弃全局表,实施适用于多用户情况的功能。这很高风险,因为如果您搞砸了实现,就没有任何防御。
  3. 与老板交谈。告诉他们您遇到了ORA-14452错误,说您已经进行了一些调查,并且使用全局临时表在这种方式下出现了根本性问题,但显然您已经忽略了某些东西。然后,问他们在以前实施时如何解决此问题。这可以有几种方法,也许他们有一个解决方法,也许他们会意识到这是错误使用全局临时表的方式,也许他们会告诉您走开。无论哪种方式,这都是最好的方法:您已按适当的级别提出了关注。

祝你好运。


不,还有另一个问题,代码中有一个我没有调用的过程,该过程将更改数据并将其插入到另一个全局临时表“d2v_temp”中。有两个表,一个是v2d_temp,另一个是d2v_temp,一个是我的数据,另一个是过程转换后的数据。过程将从d2v_temp读取数据并将其转换后放入v2d_temp中。 - Naeem Baghi
1
您提供的细节越多,这个问题似乎不需要全局临时表。如果您的老板告诉您要使用它们,那么这是一个政治问题而不是技术问题,超出了我们的职责范围。 - APC
我知道这是一个政治问题,但我的问题是关于寻找答案的:在Oracle中是否有任何方法可以删除依赖于会话的全局临时表?如果我们有这样的方法,我的问题就解决了。 - Naeem Baghi
当然,你可能会遇到和我一样的情况,你的老板或其他人坚持按照他们的方式做某些事情,所以你无法改变这种情况,你只能使用一些特殊的方法或一些技巧,以便你可以用“肮脏”的方式完成任务! - Naeem Baghi

5
杀死会话是解决ORA-14452错误的唯一方法。使用数据字典查找使用临时表的其他会话,并使用以下语句杀死它们:alter system kill session 'sid,seriall#,instance_id';
这是在Oracle支持文档《如何诊断临时表删除期间的ORA-14452错误》(文档ID 800506.1)中提到的“官方”解决方案。我曾经成功地用过这种方法,但原因略有不同。杀死会话需要提升权限并且可能比较麻烦;可能需要多次尝试杀死、等待和重试。
出于许多原因,这个解决方案几乎肯定是一个坏主意。在实施此方法之前,您应该尝试利用这些信息作为证据,表明这是错误的做法。例如,“Oracle文档表示,此方法需要alter system权限,这很危险并引发了一些安全问题...”。

如果我关闭会话,是否会影响其他用户,或者会对运行在Tomcat 6.0上的整个应用程序造成问题? - Naeem Baghi
哦,是的,杀死会话非常危险,可能会引起各种问题。应用程序杀死会话非常罕见。这是使其工作的唯一方法,但我并不是建议您这样做。相反,您可能想向您的数据库管理员提及此事;他们可能会坚持要求您找到另一种方法。 - Jon Heller
幸运的是,这项任务不再由我来完成了,他已经把它分配给另一个人了。他发现这样做也不可行,所以我们需要重新讨论如何实现这个任务。 - Naeem Baghi

4
另一个值得考虑的方法是重新考虑您是否需要临时表。在从其他RDBMS转换到Oracle的人中,这是一种非常常见的编程实践,因为他们不明白您可以使用诸如通用表达式等功能来隐式地材料化临时结果集,该结果集可以在同一查询的其他部分以及其他系统中被引用。在其他系统中,将数据写入表中然后从中选择已成为自然。失败通常会加剧,因为不理解基于PL / SQL逐行处理在几乎所有方面都比基于SQL集处理劣 -- 更慢,编码更复杂,更冗长,容易出错--但是Oracle为SQL处理提供了许多其他强大的功能,即使在需要时,它也通常直接集成到SQL SELECT语句中。顺便说一下,在为报告和ETL编写Oracle代码的20年中,我只需要使用逐行处理一次,而且从未需要使用临时表。

0

您可以通过运行以下命令来查看所有正在运行的会话:

SELECT * FROM V$SESSION

要终止一个会话,您有几个选项。以下命令在断开连接之前等待当前正在进行的事务完成:
ALTER SYSTEM DISCONNECT SESSION ‘sid,serial#’ POST_TRANSACTION

虽然以下命令与kill -9类似,但它会彻底清除操作系统进程:

ALTER SYSTEM DISCONNECT SESSION ‘sid,serial#’ IMMEDIATE

最有效的方法是使用后者来终止阻止您删除临时表的会话。但是,要小心使用,因为它是一种相当粗暴的函数。一旦会话被终止,您就可以删除临时表而不会出现错误。

您可以在此处阅读有关终止会话的不同方法的更多信息(我与该网站无关,当我遇到类似问题时自己发现了它): https://chandlerdba.wordpress.com/2013/07/25/killing-a-session-dead/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接