如何在Oracle存储过程中捕获运行时

时间:2019-07-18 18:49:55

标签: sql oracle stored-procedures

我想记录Oracle中某些SELECT存储过程的运行时间。我将其分解为以下步骤。

  • 第1步-获取开始时间
  • 第2步-将proc正在运行的日志插入到日志表中
  • 第3步-获取插入的RowId
  • 第4步-在Proc中运行SELECT语句
  • 第5步-获取结束时间
  • 第5步-使用“总运行时间”更新插入的行(在LOG表中)。

重要说明:SELECT语句需要几分钟才能运行。

这是怎么回事:

  1. 程序运行
  2. 将一行插入到LOG表中
  3. LOG表立即更新为总运行时间。
  4. SELECT语句继续需要5分钟才能运行
  5. SELECT语句完成后,结果数据最终返回。

在整个过程完成之前,不应更新LOG表。

基本上发生的是该过程立即插入,然后在SELECT语句完成的“之前”更新LOG表。

我尝试包装和嵌套其他BEGIN和END语句。在SELECT语句返回之前,存储过程仍在过程结束时运行“ UPDATE”语句。

CREATE OR REPLACE EDITIONABLE PROCEDURE SP_CUSTOMERDATA_GET ( 
   PARAM_USERID       IN    VARCHAR2,
   PARAM_FIRSTNAME    IN    VARCHAR2,   
   PARAM_LASTNAME    IN     VARCHAR2      
   OUTPUT              OUT types.cursor_type)
AS
BEGIN

DECLARE
l_Id Number;
l_StartTime TIMESTAMP;
l_EndTime TIMESTAMP;
l_TotalTime Number;

BEGIN

    l_StartTime:= systimestamp;

    INSERT INTO PROC_LOG (SPNAME, PARM1, PARM2, PARM3)
    VALUES ('SP_CUSTOMERDATA_GET',I_USERNAME, PARAM_USERID, PARAM_FIRSTNAME, PARAM_LASTNAME)
    RETURNING ID INTO l_Id;
    COMMIT;

    OPEN OUTPUT FOR 

    SELECT * 
     FROM CUSTOMER 
    WHERE USERID=PARAM_USERID
      AND FIRSTNAME=PARAM_FIRSTNAME 
      AND LASTNAME=PARAM_LASTNAME; 

  l_EndTime:= systimestamp;      
  l_TotalTime:=  extract(second from (l_EndTime-l_StartTime));

  --ISSUE: This statement runs before the SELECT statement above completes
  UPDATE PROC_LOG
  SET RUNTIME_SECONDS=l_TotalTime
  WHERE ID=l_Id;  
  COMMIT;        


END;

END SP_CUSTOMERDATA_GET;

我可以在PROC中设置一个属性,以强制该过程在上一个命令完成之前不运行下一个命令。程序没有按顺序运行没有意义吗?

2 个答案:

答案 0 :(得分:1)

问题在于您的过程实际上并未运行SELECT语句。您的过程仅打开分析该语句并获取该语句句柄的游标。它不会导致数据库实际执行该语句。当调用者从返回的游标中获取数据时,就会发生这种情况。该过程完成后,将不知道调用方是否要从游标中获取数据,是否仅要获取前10行,还是最终是否要获取每一行。如果您的目标是测量从游标中获取数据所花费的时间,则希望将日志记录添加到调用方,而不是此过程。

当然,您也可以单独运行SELECT语句。如果实际查询与您发布的内容接近,我强烈建议您缺少customer上的索引。我猜想userID是唯一的,因此如果userID上有一个索引,该查询应该在几毫秒内运行。

答案 1 :(得分:1)

我重新阅读了问题和贾斯汀的评论,并根据他的建议提出了代码解决方案。 首先是数据库结构的常规设置:

FSITJA@db01>create table customer (userid,
  2                         firstname,
  3                         lastname) as
  4  select level, 'John', 'Doe'
  5    from dual
  6  connect by level <= 1000000;

Table created.

FSITJA@db01>create table PROC_LOG (id number generated as identity,
  2                         SPNAME varchar2(30),
  3                         PARM1 varchar2(100),
  4                         PARM2 varchar2(100),
  5                         PARM3 varchar2(100),
  6                         RUNTIME_SECONDS number);

Table created.

FSITJA@db01>create or replace type tp_customer_row as object (userid  number,
  2                                                    firstname varchar2(100),
  3                                                    lastname  varchar2(100));
  4  /

Type created.

FSITJA@db01>create or replace type tp_customer as table of tp_customer_row;
  2  /

Type created.

FSITJA@db01>create or replace package types as
  2    type cursor_type is ref cursor return customer%rowtype;
  3  end;
  4  /

Package created.

然后,我们将需要一个具有自治事务的存储过程来记录时间,并使用表函数来查询集合中的数据。我们可以将光标传递到Select中的函数中以测试其功能:

FSITJA@db01>create or replace procedure sp_log_customerdata_get(proc_log_id in proc_log.id%type, starttime in timestamp) as
  2    pragma autonomous_transaction;
  3  begin
  4    UPDATE PROC_LOG
  5       SET RUNTIME_SECONDS=extract(second from (systimestamp-starttime))
  6     WHERE ID=proc_log_id;
  7    COMMIT;
  8  end;
  9  /

Procedure created.
FSITJA@db01>create or replace function fn_customerdata_get(cust_cursor types.cursor_type,
  2                                                 proc_log_id in proc_log.id%type,
  3                                                 starttime   in timestamp) return tp_customer
  4  pipelined as
  5    in_cust_rec  customer%rowtype;
  6    out_cust_rec tp_customer_row := tp_customer_row(null, null, null);
  7  begin
  8    loop
  9      fetch cust_cursor into in_cust_rec;
 10      exit when cust_cursor%notfound;
 11      out_cust_rec.userid    := in_cust_rec.userid;
 12      out_cust_rec.firstname := in_cust_rec.firstname;
 13      out_cust_rec.lastname  := in_cust_rec.lastname;
 14      pipe row(out_cust_rec);
 15    end loop;
 16    close cust_cursor;
 17    sp_log_customerdata_get(proc_log_id, starttime);
 18    return;
 19  end;
 20  /

Function created.

FSITJA@db01>select *
  2    from table(fn_customerdata_get(cursor(select userid,
  3                                                 firstname,
  4                                                 lastname
  5                                            from customer
  6                                           where rownum < 5),
  7                                   null,
  8                                   systimestamp));

    USERID FIRSTNAME       LASTNAME
---------- --------------- ---------------
         1 John            Doe
         2 John            Doe
         3 John            Doe
         4 John            Doe

现在,原始过程将调用传递ref游标的函数,然后将此游标在其参数中转发给客户端应用程序:

FSITJA@db01>CREATE OR REPLACE PROCEDURE SP_CUSTOMERDATA_GET (
  2     PARAM_USERID       IN    VARCHAR2,
  3     PARAM_FIRSTNAME    IN    VARCHAR2,
  4     PARAM_LASTNAME     IN    VARCHAR2,
  5     OUTPUT             OUT   types.cursor_type) AS
  6    l_Id Number;
  7    l_StartTime TIMESTAMP;
  8    l_EndTime TIMESTAMP;
  9    l_TotalTime Number;
 10    l_CustResult tp_customer;
 11  BEGIN
 12    l_StartTime:= systimestamp;
 13    INSERT INTO PROC_LOG (SPNAME, PARM1, PARM2, PARM3)
 14    VALUES ('SP_CUSTOMERDATA_GET', PARAM_USERID, PARAM_FIRSTNAME, PARAM_LASTNAME)
 15    RETURNING ID INTO l_Id;
 16    COMMIT;
 17    open output for
 18    select *
 19      from table(fn_customerdata_get(cursor(SELECT userid,
 20                                                   firstname,
 21                                                   lastname
 22                                              FROM CUSTOMER
 23                                             WHERE USERID=PARAM_USERID
 24                                               AND FIRSTNAME=PARAM_FIRSTNAME
 25                                               AND LASTNAME=PARAM_LASTNAME),
 26                                             l_Id,
 27                                             l_StartTime
 28                                    )
 29                );
 30  END SP_CUSTOMERDATA_GET;
 31  /

Procedure created.

最后是一段代码来测试,只有在客户端应用程序从表函数中获取数据之后,才会有经过时间的日志条目:

FSITJA@db01>declare
  2    v_output types.cursor_type;
  3    v_runtime_seconds number;
  4    type tp_cust_table is table of customer%rowtype;
  5    v_cust_table tp_cust_table;
  6  begin
  7    SP_CUSTOMERDATA_GET (1, 'John', 'Doe', v_output);
  8    select runtime_seconds
  9      into v_runtime_seconds
 10      from proc_log
 11     where id = 1;
 12    dbms_output.put_line('Runtime before client fetches: ' || v_runtime_seconds);
 13    fetch v_output
 14      bulk collect into v_cust_table;
 15    select runtime_seconds
 16      into v_runtime_seconds
 17      from proc_log
 18     where id = 1;
 19    dbms_output.put_line('Runtime AFTER client fetches: ' || v_runtime_seconds);
 20  end;
 21  /
Runtime before client fetches:
Runtime AFTER client fetches: .118791

PL/SQL procedure successfully completed.