ORACLE中的睡眠功能

发布于 2024-08-27 22:43:17 字数 467 浏览 10 评论 0原文

我需要在 ORACLE 中执行 SQL 查询,这需要一定的时间。 所以我写了这个函数:

CREATE OR REPLACE FUNCTION MYSCHEMA.TEST_SLEEP
(
TIME_  IN  NUMBER
)
RETURN INTEGER IS
 BEGIN
   DBMS_LOCK.sleep(seconds => TIME_);
RETURN 1;
 EXCEPTION
   WHEN OTHERS THEN
   RAISE;
   RETURN 1;
END TEST_SLEEP;

我以这种方式调用,

SELECT TEST_SLEEP(10.5) FROM DUAL

但为了工作,我需要向过程的所有者设置DBMS_LOCK的授予。

如何在不使用 DBMS_LOCK.sleep 函数的情况下重写此函数?

I need execute an SQL query in ORACLE it takes a certain amount of time.
So I wrote this function:

CREATE OR REPLACE FUNCTION MYSCHEMA.TEST_SLEEP
(
TIME_  IN  NUMBER
)
RETURN INTEGER IS
 BEGIN
   DBMS_LOCK.sleep(seconds => TIME_);
RETURN 1;
 EXCEPTION
   WHEN OTHERS THEN
   RAISE;
   RETURN 1;
END TEST_SLEEP;

and I call in this way

SELECT TEST_SLEEP(10.5) FROM DUAL

but to work I need set grant of DBMS_LOCK to the owner of the procedure.

How I can rewrite this function without using the DBMS_LOCK.sleep function?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

夏日浅笑〃 2024-09-03 22:43:17

如果没有授予对 DBMS_LOCK.sleep 的访问权限,这会起作用,但这是一个可怕的黑客行为:

IN_TIME INT; --num seconds
v_now DATE;

-- 1) Get the date & time 
SELECT SYSDATE 
  INTO v_now
  FROM DUAL;

-- 2) Loop until the original timestamp plus the amount of seconds <= current date
LOOP
  EXIT WHEN v_now + (IN_TIME * (1/86400)) <= SYSDATE;
END LOOP;

Short of granting access to DBMS_LOCK.sleep, this will work but it's a horrible hack:

IN_TIME INT; --num seconds
v_now DATE;

-- 1) Get the date & time 
SELECT SYSDATE 
  INTO v_now
  FROM DUAL;

-- 2) Loop until the original timestamp plus the amount of seconds <= current date
LOOP
  EXIT WHEN v_now + (IN_TIME * (1/86400)) <= SYSDATE;
END LOOP;
东走西顾 2024-09-03 22:43:17

创建一个仅执行锁定操作的过程,并将其安装到另一个用户,该用户是 dbms_lock ( USERA )“信任”的用户,授予 USERA 对 dbms_lock 的访问权限。

然后只需授予 USERB 访问此功能的权限即可。然后,他们不需要能够访问 DBMS_LOCK

(在运行此命令之前,请确保您的系统中没有 usera 和 userb)

以具有 dbms_lock 的 grant privs 的用户身份连接,并且可以创建用户

drop user usera cascade;
drop user userb cascade;
create user usera default tablespace users identified by abc123;
grant create session to usera;
grant resource to usera;
grant execute on dbms_lock to usera;

create user userb default tablespace users identified by abc123;
grant create session to userb;
grant resource to useb

connect usera/abc123;

create or replace function usera.f_sleep( in_time number ) return number is
begin
 dbms_lock.sleep(in_time);
 return 1;
end;
/

grant execute on usera.f_sleep to userb;

connect userb/abc123;

/* About to sleep as userb */
select usera.f_sleep(5) from dual;
/* Finished sleeping as userb */

/* Attempt to access dbms_lock as userb.. Should fail */

begin
  dbms_lock.sleep(5);
end;
/

/* Finished */

Create a procedure which just does your lock and install it into a different user, who is "trusted" with dbms_lock ( USERA ), grant USERA access to dbms_lock.

Then just grant USERB access to this function. They then wont need to be able to access DBMS_LOCK

( make sure you don't have usera and userb in your system before running this )

Connect as a user with grant privs for dbms_lock, and can create users

drop user usera cascade;
drop user userb cascade;
create user usera default tablespace users identified by abc123;
grant create session to usera;
grant resource to usera;
grant execute on dbms_lock to usera;

create user userb default tablespace users identified by abc123;
grant create session to userb;
grant resource to useb

connect usera/abc123;

create or replace function usera.f_sleep( in_time number ) return number is
begin
 dbms_lock.sleep(in_time);
 return 1;
end;
/

grant execute on usera.f_sleep to userb;

connect userb/abc123;

/* About to sleep as userb */
select usera.f_sleep(5) from dual;
/* Finished sleeping as userb */

/* Attempt to access dbms_lock as userb.. Should fail */

begin
  dbms_lock.sleep(5);
end;
/

/* Finished */
时常饿 2024-09-03 22:43:17

从 Oracle 18c 开始,您可以使用 DBMS_SESSION.SLEEP过程:

此过程会将会话暂停指定的时间段。

DBMS_SESSION.SLEEP(秒数)

DBMS_SESSION.sleep 可用于所有会话,无需额外授权。
请注意,DBMS_LOCK.sleep 已弃用。

如果您需要简单的查询睡眠,您可以使用WITH FUNCTION

WITH FUNCTION my_sleep(i NUMBER)
RETURN NUMBER IS
BEGIN
    DBMS_SESSION.sleep(i);
    RETURN i;
END;
SELECT my_sleep(3) FROM dual;

db<>fiddle demo

From Oracle 18c you could use DBMS_SESSION.SLEEP procedure:

This procedure suspends the session for a specified period of time.

DBMS_SESSION.SLEEP (seconds  IN NUMBER)

DBMS_SESSION.sleep is available to all sessions with no additional grants needed.
Please note that DBMS_LOCK.sleep is deprecated.

If you need simple query sleep you could use WITH FUNCTION:

WITH FUNCTION my_sleep(i NUMBER)
RETURN NUMBER IS
BEGIN
    DBMS_SESSION.sleep(i);
    RETURN i;
END;
SELECT my_sleep(3) FROM dual;

db<>fiddle demo

惜醉颜 2024-09-03 22:43:17

关于此主题有一篇很好的文章: PL/SQL :不使用 DBMS_LOCK 就睡觉,这帮助了我。我使用了封装在自定义包中的选项 2。建议的解决方案是:

选项 1:APEX_UTIL.sleep

如果安装了 APEX,您可以使用公开可用的软件包 APEX_UTIL 中的过程“PAUSE”。

示例 - “等待 5 秒”:

SET SERVEROUTPUT ON ;
BEGIN
    DBMS_OUTPUT.PUT_LINE('Start ' || to_char(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
    APEX_UTIL.PAUSE(5);
    DBMS_OUTPUT.PUT_LINE('End   ' || to_char(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
END;
/

选项2:java.lang.Thread.sleep

另一个选择是使用 Java 类“Thread”中的方法“sleep”,您可以通过提供简单的 PL/SQL 包装程序轻松使用该方法:

注意:请记住,“Thread.sleep”使用毫秒!

--- create ---
CREATE OR REPLACE PROCEDURE SLEEP (P_MILLI_SECONDS IN NUMBER) 
AS LANGUAGE JAVA NAME 'java.lang.Thread.sleep(long)';

--- use ---
SET SERVEROUTPUT ON ;
BEGIN
    DBMS_OUTPUT.PUT_LINE('Start ' || to_char(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
    SLEEP(5 * 1000);
    DBMS_OUTPUT.PUT_LINE('End   ' || to_char(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
END;
/

There is a good article on this topic: PL/SQL: Sleep without using DBMS_LOCK that helped me out. I used Option 2 wrapped in a custom package. Proposed solutions are:

Option 1: APEX_UTIL.sleep

If APEX is installed you can use the procedure “PAUSE” from the publicly available package APEX_UTIL.

Example – “Wait 5 seconds”:

SET SERVEROUTPUT ON ;
BEGIN
    DBMS_OUTPUT.PUT_LINE('Start ' || to_char(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
    APEX_UTIL.PAUSE(5);
    DBMS_OUTPUT.PUT_LINE('End   ' || to_char(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
END;
/

Option 2: java.lang.Thread.sleep

An other option is the use of the method “sleep” from the Java class “Thread”, which you can easily use through providing a simple PL/SQL wrapper procedure:

Note: Please remember, that “Thread.sleep” uses milliseconds!

--- create ---
CREATE OR REPLACE PROCEDURE SLEEP (P_MILLI_SECONDS IN NUMBER) 
AS LANGUAGE JAVA NAME 'java.lang.Thread.sleep(long)';

--- use ---
SET SERVEROUTPUT ON ;
BEGIN
    DBMS_OUTPUT.PUT_LINE('Start ' || to_char(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
    SLEEP(5 * 1000);
    DBMS_OUTPUT.PUT_LINE('End   ' || to_char(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
END;
/
稍尽春風 2024-09-03 22:43:17

如果在“sqlplus”中执行,您可以执行主机操作系统命令“sleep”:

!sleep 1

host sleep 1

If executed within "sqlplus", you can execute a host operating system command "sleep" :

!sleep 1

or

host sleep 1
逆流 2024-09-03 22:43:17

过程包装的 Java 代码怎么样?简单且运行良好。

CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED SNOOZE AS
public final class Snooze {
  private Snooze() {
  }
  public static void snooze(Long milliseconds) throws InterruptedException {
      Thread.sleep(milliseconds);
  }
}

CREATE OR REPLACE PROCEDURE SNOOZE(p_Milliseconds IN NUMBER) AS
    LANGUAGE JAVA NAME 'Snooze.snooze(java.lang.Long)';

What's about Java code wrapped by a procedure? Simple and works fine.

CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED SNOOZE AS
public final class Snooze {
  private Snooze() {
  }
  public static void snooze(Long milliseconds) throws InterruptedException {
      Thread.sleep(milliseconds);
  }
}

CREATE OR REPLACE PROCEDURE SNOOZE(p_Milliseconds IN NUMBER) AS
    LANGUAGE JAVA NAME 'Snooze.snooze(java.lang.Long)';
零時差 2024-09-03 22:43:17

最好实现一个同步机制。最简单的方法是在第一个文件完成后写入一个文件。所以你有一个哨兵文件。

因此外部程序会寻找哨兵文件是否存在。当它这样做时,它知道它可以安全地使用真实文件中的数据。

另一种方法与某些浏览器下载文件时的做法类似,是将文件命名为 base-name_part,直到文件完全下载,然后最后将文件重命名为 base-name。
这样,外部程序在文件完成之前无法“查看”该文件。这种方式不需要重写外部程序。这可能最适合这种情况。

It would be better to implement a synchronization mechanism. The easiest is to write a file after the first file is complete. So you have a sentinel file.

So the external programs looks for the sentinel file to exist. When it does it knows that it can safely use the data in the real file.

Another way to do this, which is similar to how some browsers do it when downloading files, is to have the file named base-name_part until the file is completely downloaded and then at the end rename the file to base-name.
This way the external program can't "see" the file until it is complete. This way wouldn't require rewrite of the external program. Which might make it best for this situation.

一花一树开 2024-09-03 22:43:17

您可以按如下方式使用DBMS_ALERT包:

CREATE OR REPLACE FUNCTION sleep(seconds IN NUMBER) RETURN NUMBER
AS
    PRAGMA AUTONOMOUS_TRANSACTION;
    message VARCHAR2(200);
    status  INTEGER;
BEGIN
    DBMS_ALERT.WAITONE('noname', message, status, seconds);
    ROLLBACK;
    RETURN seconds;
END;
SELECT sleep(3) FROM dual;

You can use the DBMS_ALERT package as follows:

CREATE OR REPLACE FUNCTION sleep(seconds IN NUMBER) RETURN NUMBER
AS
    PRAGMA AUTONOMOUS_TRANSACTION;
    message VARCHAR2(200);
    status  INTEGER;
BEGIN
    DBMS_ALERT.WAITONE('noname', message, status, seconds);
    ROLLBACK;
    RETURN seconds;
END;
SELECT sleep(3) FROM dual;
最单纯的乌龟 2024-09-03 22:43:17

如果 Java 安装在您的 11G 上,那么您可以在 java 类中执行此操作并从 PL/SQL 中调用它,但我不确定它是否也不需要特定的授权来调用 java。

If Java is installed on your 11G then you can do it in a java class and call it from your PL/SQL, but I am not sure that it does not require also a specific grant to call java.

玩世 2024-09-03 22:43:17

您可以将 DBMS_PIPE.SEND_MESSAGE 与对于管道来说太大的消息一起使用,例如,使用 5 秒超时将 XXX 延迟 5 秒写入只能接受一个字节的管道,如下所示

dbms_pipe.pack_message('XXX');<br>
dummy:=dbms_pipe.send_message('TEST_PIPE', 5, 1);

但是那么这需要 DBMS_PIPE 的资助,所以也许没有更好的了。

You can use DBMS_PIPE.SEND_MESSAGE with a message that is too large for the pipe, for example for a 5 second delay write XXX to a pipe that can only accept one byte using a 5 second timeout as below

dbms_pipe.pack_message('XXX');<br>
dummy:=dbms_pipe.send_message('TEST_PIPE', 5, 1);

But then that requires a grant for DBMS_PIPE so perhaps no better.

绝影如岚 2024-09-03 22:43:17

看来java过程/函数可以工作。但是为什么不在像应用程序架构这样的用户或具有此授权的管理员帐户下编译您的函数,并只授予您的开发人员帐户在其上执行。这样就可以使用定义者权限。

Seems the java procedure/function could work. But why don't you compile your function under a user like the application schema or a admin account that has this grant and just grant your developer account execute on it. That way the definer rights are used.

℡寂寞咖啡 2024-09-03 22:43:17

这是我的解决方案:

declare
   vFecIni date;
   vFecFin date;
   vInicio number;
   vFinal  number;

   function milisegundos return number is
      vRes number;
   begin
      select round(
              ( (horas         * 3600000000)
              + (minutos       *   60000000)
              + (segundos      *    1000000)
              + microsegundos ) / 1000 )
         into vRes
         from ( select to_number(substr(tiempo, 1,2)) as horas,
                       to_number(substr(tiempo, 4,2)) as minutos,
                       to_number(substr(tiempo, 7,2)) as segundos,
                       to_number(substr(tiempo,  10)) as microsegundos
                 from ( select to_char(ct, 'HH24:MI:SS.FF') as tiempo
                          from (select current_timestamp as ct from dual) ) );
      
      return vRes;
      
   end milisegundos;
   
   function ms_to_char(pMs number) return varchar2 is
       CMILIS constant number:= 1000;
       CMINSEC constant number:= 60;
       CHORMIN constant number:= 60;
       vHoras number:= 0;
       vMinutos number:= 0;
       vSegundos number:= 0;
       vMilisegundos number:= 0;
       vResto number:= 0;
       vRes varchar2(32767);
    begin
    --
    -- Milisegundos
       vMilisegundos:= pMs;
       
    --
    -- Segundos
       if CMILIS < vMilisegundos then
          vResto:= mod(vMilisegundos, CMILIS);
          vSegundos:= (vMilisegundos - vResto) / CMILIS;
          vMilisegundos:= vResto;
          
       --
       -- Minutos
          if CMINSEC < vSegundos then
             vResto:= mod(vSegundos, CMINSEC);
             vMinutos:= (vSegundos - vResto) / CMINSEC;
             vSegundos:= vResto;
             
          --
          -- Horas
             if CHORMIN < vMinutos then
                vResto:= mod(vMinutos, CHORMIN);
                vHoras:= (vMinutos - vResto) / CHORMIN;
                vMinutos:= vResto;
              
             end if; -- Horas
          
          end if; -- Minutos
          
       end if; -- Segundos
       
    --
    -- Resultado en formato texto: HH24:MI:SS.FF
       vRes:= lpad(vHoras,        2, '0') || ':'
           || lpad(vMinutos,      2, '0') || ':'
           || lpad(vSegundos,     2, '0') || '.'
           || lpad(vMilisegundos, 3, '0');
           
       return vRes;

    end ms_to_char;
   
   procedure parada_de(de number) is
      CSEGUNDO constant number:= 1000;
      vFec date:= sysdate;
      vIni number:= milisegundos;
      vFin number;
      vMax number:= de * CSEGUNDO;
      vTot number;
   begin
      vFecIni:= vFec;
      
      loop
         vFin:= milisegundos;
         vTot:= vFin - vIni;
         exit when vTot > vMax;
      end loop;
      
      vFecFin:= sysdate;
      
   end parada_de;
   
begin
   vInicio:= milisegundos;
   parada_de(2);
   vFinal:= milisegundos;
   dbms_output.put_line(  'Inicia en '      || to_char(vFecIni,'hh24:mi:ss') || ' (' || vInicio
                       || '), finaliza en ' || to_char(vFecFin,'hh24:mi:ss') || ' (' || vFinal
                       || '), duró '        || ms_to_char(vFinal - vInicio)
                      );
end;

this is my solution:

declare
   vFecIni date;
   vFecFin date;
   vInicio number;
   vFinal  number;

   function milisegundos return number is
      vRes number;
   begin
      select round(
              ( (horas         * 3600000000)
              + (minutos       *   60000000)
              + (segundos      *    1000000)
              + microsegundos ) / 1000 )
         into vRes
         from ( select to_number(substr(tiempo, 1,2)) as horas,
                       to_number(substr(tiempo, 4,2)) as minutos,
                       to_number(substr(tiempo, 7,2)) as segundos,
                       to_number(substr(tiempo,  10)) as microsegundos
                 from ( select to_char(ct, 'HH24:MI:SS.FF') as tiempo
                          from (select current_timestamp as ct from dual) ) );
      
      return vRes;
      
   end milisegundos;
   
   function ms_to_char(pMs number) return varchar2 is
       CMILIS constant number:= 1000;
       CMINSEC constant number:= 60;
       CHORMIN constant number:= 60;
       vHoras number:= 0;
       vMinutos number:= 0;
       vSegundos number:= 0;
       vMilisegundos number:= 0;
       vResto number:= 0;
       vRes varchar2(32767);
    begin
    --
    -- Milisegundos
       vMilisegundos:= pMs;
       
    --
    -- Segundos
       if CMILIS < vMilisegundos then
          vResto:= mod(vMilisegundos, CMILIS);
          vSegundos:= (vMilisegundos - vResto) / CMILIS;
          vMilisegundos:= vResto;
          
       --
       -- Minutos
          if CMINSEC < vSegundos then
             vResto:= mod(vSegundos, CMINSEC);
             vMinutos:= (vSegundos - vResto) / CMINSEC;
             vSegundos:= vResto;
             
          --
          -- Horas
             if CHORMIN < vMinutos then
                vResto:= mod(vMinutos, CHORMIN);
                vHoras:= (vMinutos - vResto) / CHORMIN;
                vMinutos:= vResto;
              
             end if; -- Horas
          
          end if; -- Minutos
          
       end if; -- Segundos
       
    --
    -- Resultado en formato texto: HH24:MI:SS.FF
       vRes:= lpad(vHoras,        2, '0') || ':'
           || lpad(vMinutos,      2, '0') || ':'
           || lpad(vSegundos,     2, '0') || '.'
           || lpad(vMilisegundos, 3, '0');
           
       return vRes;

    end ms_to_char;
   
   procedure parada_de(de number) is
      CSEGUNDO constant number:= 1000;
      vFec date:= sysdate;
      vIni number:= milisegundos;
      vFin number;
      vMax number:= de * CSEGUNDO;
      vTot number;
   begin
      vFecIni:= vFec;
      
      loop
         vFin:= milisegundos;
         vTot:= vFin - vIni;
         exit when vTot > vMax;
      end loop;
      
      vFecFin:= sysdate;
      
   end parada_de;
   
begin
   vInicio:= milisegundos;
   parada_de(2);
   vFinal:= milisegundos;
   dbms_output.put_line(  'Inicia en '      || to_char(vFecIni,'hh24:mi:ss') || ' (' || vInicio
                       || '), finaliza en ' || to_char(vFecFin,'hh24:mi:ss') || ' (' || vFinal
                       || '), duró '        || ms_to_char(vFinal - vInicio)
                      );
end;
濫情▎り 2024-09-03 22:43:17

您可以使用 DBMS_SESSION.SLEEP 替换 Oracle Database 18c 中的 DBMS_LOCK.SLEEP
https://oracle-base.com/articles/18c/dbms_session- sleep-18c#dbms_session

you can use DBMS_SESSION.SLEEP which replaces DBMS_LOCK.SLEEP in Oracle Database 18c
https://oracle-base.com/articles/18c/dbms_session-sleep-18c#dbms_session

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文