【问题标题】:Rollback procedure creating file回滚过程创建文件
【发布时间】:2017-01-03 16:24:16
【问题描述】:

我有几个使用 UTL_FILE 将表导出到文件中的 PL/SQL 过程。

这是一个快照:

PROCEDURE export_t1
  AS
    l_file      UTL_FILE.FILE_TYPE;
    record       VARCHAR2(4096);
  BEGIN

    l_file := UTL_FILE.FOPEN(DIRECTORY_PATH, FILENAME, 'A');

    FOR j IN
    (SELECT * FROM PRODUCTS WHERE HANDLE = '0')
    LOOP
     l_record := j.id || ',' || j.code || ',' || j.desc ....... [others fields];
     UTL_FILE.PUT_LINE(l_file,l_record);
    END LOOP;

    UTL_FILE.FCLOSE(l_file);
    UPDATE PRODUCTS SET HANDLE = '1' WHERE HANDLE = '0';

  EXCEPTION
  WHEN OTHERS THEN
    -- log
   RAISE;
  END export_t1;

所以我有export_t1、export_t2、export_tn 程序。此外,我在一个“主”过程中依次调用它们..

我的问题是..如果我在 export_t2 中有异常,这是第二个 程序,如何阻止第一个(export_t1)创建文件

想法是..在所有程序都正常时创建文件,没有例外

【问题讨论】:

    标签: oracle plsql export procedure


    【解决方案1】:

    除非您可以让您的文件系统参与两阶段提交(据我所知,目前还不可能),否则将文件输出与您的数据库事务协调起来将会很困难,因为您的文件操作位于数据库事务的范围。

    即,总是存在这样一种理论场景,即某些事情发生在完全错误的时间,并且您的数据库和文件系统不同步。 (有点让你感激COMMIT为我们所做的一切)。

    无论如何,一个可能的策略是设计一些东西,让出错的窗口尽可能短。例如,

    begin
      delete_real_files;  -- delete leftovers.
      write_temp_file_n1;
      write_temp_file_n2;
      write_temp_file_n3;
      ...
      write_temp_file_nx;
    
      rename_temp_files_to_real;
    
      commit;
    
      -- don't do anything else with the files after this point
    
    exception
      when others then
        remove_real_files;
        remove_temp_files;
        rollback;
    end;
    

    这里的想法是将所有文件写入临时文件。如果出现故障,则清理它们。没有进程可以看到“真实”文件,因为您从未创建过它们。只有在最后,您才能通过重命名临时文件来实现它们。

    您的风险是您的前几个临时文件已成功重命名,但随后的临时文件无法重命名并且(A)一个进程在您的异常处理程序可以删除它们之前跳转并看到它们或(B)异常处理程序由于某种原因无法删除它们。

    我喜欢这种方法,因为它将所有风险都与重命名文件联系起来,这是一种非常安全的操作(因为它不需要额外的磁盘空间)。有些重命名成功而有些失败的可能性不大。

    这种方法有很多变化是可能的。但要记住的是,您并没有在这里实施坚如磐石的解决方案。总是有可能出现问题,因此根据您的容错能力,实施所需的任何检查(系统中的其他地方)。

    【讨论】:

    • 你好,事实上可能会发生一些事情..我无法处理所有事情,所以我必须尽量减少情况谢谢你的回答,我有一些想法;)
    【解决方案2】:

    UTL_FILE.FCLOSE(或UTL_FILE.FFLUSH)字面上写入磁盘。如果您不想写入磁盘,则不得写入磁盘 - 在将所有数据写入每个单独的缓冲区之前,不要关闭或刷新文件处理程序。

    根据 n 的大小,您可能会拥有许多打开文件处理程序并缓冲大量数据。这不会很漂亮。

    最好创建另一个过程来调用UTL_FILE.FREMOVE,它会删除一个命名文件(假设有足够的权限)。

    我会在Oracle scheduler 中执行此操作,因为每个过程都是链中的一个单独步骤,您可以define a rule 使用scheduler chain condition syntax 调用该过程以删除链中错误的文件。

    【讨论】:

      【解决方案3】:

      迈克尔,

      您可能可以使用 --> utl_file.fremove(DIRECTORY_PATH,FILENAME);在异常中以删除文件。

      示例代码如下。

      程序1:

      CREATE OR REPLACE PROCEDURE SHAREFLE IS
      v_MyFileHandle UTL_FILE.FILE_TYPE;
      BEGIN
      v_MyFileHandle := UTL_FILE.FOPEN('TEST_DIR','HELLO.TXT','a');
      UTL_FILE.PUT_LINE(v_MyFileHandle, 'Hello Again for the Second Time! ' ||      
      TO_CHAR(SYSDATE,'MM-DD-YY HH:MI:SS AM'));
      UTL_FILE.FCLOSE(v_MyFileHandle);
      SHAREFLE1;
      EXCEPTION
      WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE
      ('ERROR ' || TO_CHAR(SQLCODE) || SQLERRM);
      NULL; 
      END; 
      

      程序2:

       CREATE OR REPLACE PROCEDURE SHAREFLE1 IS
       v_MyFileHandle UTL_FILE.FILE_TYPE;
       BEGIN
       v_MyFileHandle := UTL_FILE.FOPEN('TEST_DIR','HELLO.TXT','a');
         UTL_FILE.PUT_LINE(v_MyFileHandle, 'Hello Again for the Third Time! ' ||    TO_CHAR(SYSDATE,'MM-DD-YY HH:MI:SS AM'));
         UTL_FILE.FCLOSE(v_MyFileHandle);
         EXCEPTION
         WHEN OTHERS THEN
         DBMS_OUTPUT.PUT_LINE
         ('ERROR ' || TO_CHAR(SQLCODE) || SQLERRM);
         utl_file.fremove('TEST_DIR','HELLO.TXT');
         NULL; 
         END; 
      

      Plsql 块调用第一个过程。

      set serveroutput on;
      begin
      sharefle;
      end;
      

      此代码是您所询问内容的一个非常简单的示例。如果有任何异常,您可以检查程序 2 是否在异常块中删除了文件“HELLO.TXT”(程序 1 和程序 2 具有相同的文件)。我已经亲自检查过,同样有效。尝试创建自己的异常并自己检查。如有任何疑问,请发表评论。

      注意:这绝不是最好的方法。我已经向您展示了我们可以这样做。谢谢:)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-05-01
        • 2011-09-02
        • 1970-01-01
        • 2023-03-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-03-22
        相关资源
        最近更新 更多