【问题标题】:How to make DB2 re-evaluate the query plan each execution?如何让 DB2 在每次执行时重新评估查询计划?
【发布时间】:2021-06-09 20:53:00
【问题描述】:

我有以下 DB2 存储过程,它允许用户指定可变数量的参数(为他们不想提供的参数指定 NULL),其中每个参数都可用于过滤 WHERE 子句中的记录。

CREATE PROCEDURE XX.DUMMY(IN I_FIRST_NAME VARCHAR(32),
                            I_LAST_NAME VARCHAR(100),
                            I_COMPANY_NAME VARCHAR(100))
SPECIFIC XX.DUMMY
RESULT SETS 1
MODIFIES SQL DATA
NOT DETERMINISTIC
NULL CALL
LANGUAGE SQL EXTERNAL ACTION
INHERIT SPECIAL REGISTERS
BEGIN
    DECLARE RESULTS CURSOR WITH RETURN TO CLIENT FOR
        WITH RELEVANT_RECORDS (RECORD_ID) AS (
            SELECT RECORD_ID
            FROM XX.RECORDS R
            WHERE (I_FIRST_NAME IS NULL OR R.SEARCH_FIRST_NAME = I_FIRST_NAME)
            AND (I_LAST_NAME IS NULL OR R.SEARCH_LAST_NAME = I_LAST_NAME)
            AND (I_COMPANY_NAME IS NULL OR R.SEARCH_COMPANY_NAME = I_COMPANY_NAME)
            WITH UR
        )
        SELECT RR.RECORD_ID, RD.*
        FROM RELEVANT_RECORDS RR
        JOIN XX.RECORD_DETAILS RD ON RD.RECORD_ID = RR.RECORD_ID
        FOR READ ONLY WITH UR;
    OPEN RESULTS;
END
;

据我了解,此过程将执行得很差,因为该过程的查询计划只会在该过程被绑定(或第一次运行?)时进行评估,如果在以后的执行中指定了不同的参数组合,原始查询计划将被使用,导致使用错误的计划/索引(从而使 proc 非常慢)。

我读到 SQL Server 允许您在过程的定义中指定 OPTION(RECOMPILE) 以告诉服务器为过程的每次执行重新评估查询计划,但是由于我使用的是 DB2,所以我正在寻找对于 DB2 等价物。

在 DB2 中有没有一种方法可以让存储过程在每次执行时重新评估它的查询计划?在运行任何语句之前,我是否必须在我的过程中使用 SET CURRENT QUERY OPTIMIZATION 之类的东西?

【问题讨论】:

    标签: sql performance stored-procedures db2 query-optimization


    【解决方案1】:

    在您的例程中使用动态 SQL(而不是静态 SQL)是确保每次都编译查询的一种方法(虽然它不同于之前的实例化,或者不在包缓存中等)。

    在这种情况下,在您的例程中,您可以在例程中动态创建的查询上使用 EXECUTE IMMEDIATE,或者使用 PREPARE 和 EXECUTE。在这两种情况下,完整查询直到执行前的运行时才存在。因此,您构建的 SQL 字符串每次都可以不同,包括(用于执行)每次不同数量的参数标记 (?) 和匹配数量的绑定参数。 SQL 查询本身不是静态的 - 每次过程运行时它都可能不同 - 随存储过程的(已清理的)参数输入而变化。

    对于静态 SQL,请查看存储过程 SET_ROUTINE_OPTS,其中 REOPT ALWAYS 需要在构建时完成,或者您可以使用注册表变量 DB2_SQLROUTINE_PREPOPTS 全局提供此指令(通常这是不明智的)。您(或 DBA)也可以安排到rebind 与 sproc 对应的静态包,并使用 REOPT ALWAYS。

    【讨论】:

    • 是否可以使用 PREPARE 和 EXECUTE 来指定带有可变数量参数的准备好的语句?我看到的所有示例都传递了一个静态参数列表,这在我的情况下不起作用(除非您将值放入数组并传入数组?)。
    • 你理解动态SQL的概念了吗?
    • 是的,我认为我的想法是正确的,我认为我现在的障碍只是弄清楚如何为 EXECUTE 语句指定可变数量的参数。看起来我可以使用 SQLDA 来完成此任务,但我不确定这是否是最佳途径。
    • 没有。您要么每次准备/执行不同的查询,要么每次都执行不同的查询。对于一个特定查询执行,没有可变数量的参数的概念,而是每次都不同的是查询本身(及其匹配的参数数量)。
    • 对,查询本身每次都不同,但是您执行的查询中将有可变数量的查询标记,在 EXECUTE 语句中,您必须仅指定主机变量与查询相关。这是我现在试图解决的主变量的变量列表,我认为 SQLDA(或数组)是答案。我不想定义一个数组对象,所以我想我可能无法使用 SQLDA,除非有更好(或更简单)的选项。
    【解决方案2】:

    您可以尝试DMBS_SQL 模块。
    对于这样一个简单的目的,它看起来有点冗长,但它确实有效。
    最终的 SELECT 语句是根据每个参数的可空性构造的。
    XML 局部变量用于累积获取的行以最终从 SP 中返回它们。您可能会使用一些 [C|G]GTT 或 ARRAY 变量来代替这些 XML 变量。

    --#SET TERMINATOR @
    
    -- Dynamic number of parameters demo
    CREATE OR REPLACE PROCEDURE TEST_DBMS_SQL
    (
      P_TABSCHEMA VARCHAR(128)
    , P_TABNAME   VARCHAR(128)
    )
    DYNAMIC RESULT SETS 1
    BEGIN
      DECLARE curid       INTEGER;
      DECLARE v_sql       VARCHAR(1000);
      DECLARE v_tabschema VARCHAR(128);
      DECLARE v_tabname   VARCHAR(128);
      DECLARE v_status    INTEGER;
      DECLARE v_doc       XML;
      DECLARE v_node      XML;
      
      DECLARE C1 CURSOR WITH RETURN FOR
      SELECT TABSCHEMA, TABNAME
      FROM XMLTABLE 
      (
        '$D/NODE' PASSING v_doc AS "D"
        COLUMNS
          TABSCHEMA VARCHAR(128) PATH 'TABSCHEMA'
        , TABNAME   VARCHAR(128) PATH 'TABNAME'
      );
      
      SET v_doc = XMLELEMENT(NAME "DOC");
      SET v_sql = 'SELECT TABSCHEMA, TABNAME FROM SYSCAT.TABLES WHERE 1=1';
      IF P_TABSCHEMA IS NOT NULL THEN
        SET v_sql = v_sql || ' AND TABSCHEMA = :p_tabschema'; 
      END IF;
      IF P_TABNAME IS NOT NULL THEN 
        SET v_sql = v_sql || ' AND TABNAME   = :p_tabname'; 
      END IF;
    
      CALL DBMS_SQL.OPEN_CURSOR(curid);
      CALL DBMS_SQL.PARSE(curid, v_sql, DBMS_SQL.native);
      
      IF P_TABSCHEMA IS NOT NULL THEN 
        CALL DBMS_SQL.BIND_VARIABLE_VARCHAR(curid, ':p_tabschema', P_TABSCHEMA);
      END IF;
      IF P_TABNAME IS NOT NULL THEN 
        CALL DBMS_SQL.BIND_VARIABLE_VARCHAR(curid, ':p_tabname', P_TABNAME);
      END IF;
    
      CALL DBMS_SQL.DEFINE_COLUMN_VARCHAR(curid, 1, v_tabschema, 128);
      CALL DBMS_SQL.DEFINE_COLUMN_VARCHAR(curid, 2, v_tabname, 128);
    
      CALL DBMS_SQL.EXECUTE(curid, v_status);
      --CALL DBMS_OUTPUT.PUT_LINE('Execute: ' || v_status);
      
      FETCH_LOOP: LOOP
      
        CALL DBMS_SQL.FETCH_ROWS(curid, v_status);
        IF v_status = 0 THEN LEAVE FETCH_LOOP; END IF;
        --CALL DBMS_OUTPUT.PUT_LINE('Fetch: ' || v_status);
        CALL DBMS_SQL.COLUMN_VALUE_VARCHAR(curid, 1, v_tabschema);
        CALL DBMS_SQL.COLUMN_VALUE_VARCHAR(curid, 2, v_tabname);
        --CALL DBMS_OUTPUT.PUT_LINE('TABSHEMA: ' || coalesce(v_tabschema, '*'));
        --CALL DBMS_OUTPUT.PUT_LINE('TABNAME: ' || coalesce(v_tabname, '*'));
        SET v_node = XMLELEMENT
        (
            NAME "NODE"
          , XMLELEMENT(NAME "TABSCHEMA", v_tabschema)
          , XMLELEMENT(NAME "TABNAME", v_tabname)
        );
      
        SET v_doc = XMLQUERY
        (
          'transform copy $mydoc := $doc modify do insert $node as last into $mydoc return $mydoc'
          passing v_doc as "doc", v_node as "node"
        );
      
      END LOOP FETCH_LOOP;
      
      --CALL DBMS_OUTPUT.PUT_LINE(XMLSERIALIZE(v_doc AS CLOB(10K)));
      CALL DBMS_SQL.CLOSE_CURSOR(curid);
      
      OPEN C1;
    END@  
    

    用法:

    call test_dbms_sql(NULL, 'TABLES')@
    call test_dbms_sql('SYSCAT', 'TABLES')@
    call test_dbms_sql('SYSCAT', NULL)@
    call test_dbms_sql(NULL, NULL)@
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-09-10
      • 2011-07-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-26
      相关资源
      最近更新 更多