【问题标题】:How to Capture Run Time in an Oracle Stored Procedure如何在 Oracle 存储过程中捕获运行时间
【发布时间】:2019-11-27 18:26:57
【问题描述】:

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

  • 第 1 步 - 获取 StartTime
  • 第 2 步 - 插入 proc 正在运行的 LOG 表中
  • 第 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 中设置一个属性,以强制该过程在前一个命令完成之前不运行下一个命令。程序不按顺序运行没有意义吗?

【问题讨论】:

  • 您在上面测量的是打开光标的时间,而不是获取数据的时间。 “打开”游标通常非常快 - 我相信它主要是用最少的 I/O 设置数据结构。这是花费实时时间的第一行获取。如果您想测量数据获取时间,您将不得不使用循环来获取数据,并且您可能需要进行特殊情况检查以确定哪个获取时间最长、最短、最高、气味最好,最有可能成功等。我建议编写一个包来执行此操作。

标签: sql oracle stored-procedures


【解决方案1】:

我重新阅读了这个问题和 Justin 的 cmets,并根据他的建议提出了一个代码解决方案。 首先是数据库结构的一般设置:

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

现在是原始过程,它将调用传递引用游标的函数,然后将该游标转发到客户端应用程序的参数中:

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.

【讨论】:

  • Francisco - 谢谢,但执行“SELECT INTO”不会返回数据。我需要几百或几千条记录的输出。
  • 嗨 Robbie,在了解了真正的需求后,我编写了一个不同的解决方案。也感谢贾斯汀。
  • 惊人的详细解决方案弗朗西斯科!这是一种不同的方法,但它有效。谢谢!
【解决方案2】:

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

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

【讨论】:

  • Justin Cave - 因此,根据您所说的,Select 语句由调用者运行,调用者必须调用某种类型的日志记录。在这种情况下是不可能的,因为调用者是一个水晶报表接口。如果您能想到在 proc 本身内执行此操作的方法,请告诉我。此外,为了回答您的问题,提供的 select 语句只是为了简化这篇文章而进行的快速模拟。谢谢。
  • @Robbieone - 如果您不介意完全重组所有内容,您可能会构建一个流水线表函数,将所有数据提取到本地集合中,记录所花费的时间(这需要记录proc 使用自治事务),然后将数据通过管道传输到客户端。这不是返回数据的最有效方式,但如果我们只讨论几千行,这可能不是一个可怕的额外开销。
猜你喜欢
  • 1970-01-01
  • 2013-12-09
  • 1970-01-01
  • 2019-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多