Oracle Database 19c引入了Runaway Query Management的扩展,称为SQL隔离。
取消失控查询有助于防止浪费系统资源,但是如果重复运行该问题查询,仍可能导致相当数量的资源浪费。SQL隔离通过隔离已取消的SQL语句来解决此问题,因此它们不能多次运行。
文章摘要:
- 先决条件
- 自动SQL隔离
- Views
- DBMS_SQLQ:SQL隔离管理
- 传输隔离区定义
先决条件
此功能目前仅限于企业版的工程系统,像数据库云服务器和Exadata云服务,如所描述这里。通过启用“ _exadata_feature_on”初始化参数,可以找到一种解决方法。不要在实际实例上使用它,否则您将违反许可协议。
export ORACLE_SID=cdb1
export ORAENV_ASK=NO
. oraenv
export ORAENV_ASK=YES
sqlplus / as sysdba <<EOF
alter system set "_exadata_feature_on"=true scope=spfile;
shutdown immediate;
startup;
exit;
EOF
测试完SQL隔离区后,请记住要重置此参数。
export ORACLE_SID=cdb1
export ORAENV_ASK=NO
. oraenv
export ORAENV_ASK=YES
sqlplus / as sysdba <<EOF
alter system reset "_exadata_feature_on" scope=spfile;
shutdown immediate;
startup;
exit;
EOF
自动SQL隔离
默认情况下,SQL隔离是可用的,因此我们不必执行任何特殊操作即可开始使用它。用于Runaway Query Management的常规资源计划将触发它。我们将使用此处 12c Runaway Query Management文章中使用的示例来演示这一点。
我们创建一个测试用户。
CONN sys/SysPassword1@//localhost:1521/pdb1 AS SYSDBA
--DROP USER testuser1 CASCADE;
CREATE USER testuser1 IDENTIFIED BY testuser1 QUOTA UNLIMITED ON users;
GRANT CREATE SESSION, CREATE PROCEDURE TO testuser1;
在下面的示例中,我们有一个资源计划,其中包含一个NORMAL_CG用于常规会话的消费者组。与NORMAL_CG使用方组的任何会话使用CPU进行一次调用超过60秒都会取消该SQL。
BEGIN
DBMS_RESOURCE_MANAGER.clear_pending_area;
DBMS_RESOURCE_MANAGER.create_pending_area;
-- Create plan
DBMS_RESOURCE_MANAGER.create_plan(
plan => 'long_running_query_plan',
comment => 'Plan to handle long running queries.');
-- Create consumer groups
DBMS_RESOURCE_MANAGER.create_consumer_group(
consumer_group => 'normal_cg',
comment => 'Consumer group for normal sessions.');
-- Assign consumer groups to plan and define priorities
DBMS_RESOURCE_MANAGER.create_plan_directive (
plan => 'long_running_query_plan',
group_or_subplan => 'normal_cg',
comment => 'Normal Priority',
mgmt_p1 => 100,
switch_group => 'CANCEL_SQL',
switch_time => 60,
switch_for_call => TRUE);
DBMS_RESOURCE_MANAGER.create_plan_directive(
plan => 'long_running_query_plan',
group_or_subplan => 'OTHER_GROUPS',
comment => 'Default',
mgmt_p2 => 100);
DBMS_RESOURCE_MANAGER.validate_pending_area;
DBMS_RESOURCE_MANAGER.submit_pending_area;
END;
/
我们允许TESTUSER1用户切换到NORMAL_CG消费者组,然后将TESTUSER1用户的初始消费者组设置为NORMAL_CG。
BEGIN
DBMS_RESOURCE_MANAGER_PRIVS.grant_switch_consumer_group(
grantee_name => 'testuser1',
consumer_group => 'normal_cg',
grant_option => FALSE);
DBMS_RESOURCE_MANAGER.set_initial_consumer_group('testuser1', 'normal_cg');
END;
/
最后,我们激活该计划。
ALTER SYSTEM SET RESOURCE_MANAGER_PLAN ='long_running_query_plan';
我们可以使用以下查询监视当前的消费者组分配。
COLUMN username FORMAT A30
COLUMN resource_consumer_group FORMAT A30
SELECT username, resource_consumer_group
FROM v$session
WHERE username = 'TESTUSER1';
让此会话保持打开状态,以便我们可以监视进度。
在一个单独的会话中,我们连接到测试用户,创建一个仅在CPU上放置指定分钟数的函数,然后查询该函数。
CONN testuser1/testuser1@//localhost:1521/pdb1
CREATE OR REPLACE FUNCTION burn_cpu (p_mins IN NUMBER)
RETURN NUMBER
AS
l_start_time DATE;
l_number NUMBER := 1;
BEGIN
l_start_time := SYSDATE;
LOOP
EXIT WHEN SYSDATE - l_start_time > (p_mins/24/60);
l_number := l_number + 1;
END LOOP;
RETURN 0;
END;
/
SELECT burn_cpu (5) FROM dual;
大约60秒钟后,调用该BURN_CPU函数的查询已取消。
SQL> SELECT burn_cpu (5) FROM dual;
Error starting at line : 1 in command -
SELECT burn_cpu (5) FROM dual
Error report -
ORA-00040: active time limit exceeded - call aborted
SQL>
文档会让您相信已取消的呼叫会立即被隔离,但实际上可能要花很长时间才能被注意到。在某些情况下,我等了15分钟以上才对已取消的语句进行隔离。显然,有些人对此感到困惑,并试图将其记录为错误。响应只是等待。
19c新功能SQL隔离不起作用(文档ID 2634990.1)
如果等待了足够长的时间,将隔离导致取消调用的执行计划,并且随后尝试使用相同的执行计划运行语句将导致隔离消息。
SQL> SELECT burn_cpu (5) FROM dual;
Error starting at line : 1 in command -
SELECT burn_cpu (5) FROM dual
Error report -
ORA-56955: quarantined plan used
SQL>
Views
我们可以在V$SQL和DBA_SQL_QUARANTINE视图中看到隔离操作的结果。
该V$SQL视图包括SQL_QUARANTINE和AVOIDED_EXECUTIONS列。我们看到的两个条目SQL_ID,一个显示AVOIDED_EXECUTIONS为0,一个显示为1。
COLUMN sql_text FORMAT A30
COLUMN sql_quarantine FORMAT A40
SELECT sql_text, sql_id, plan_hash_value, child_number, sql_quarantine, avoided_executions
FROM v$sql
WHERE sql_quarantine IS NOT NULL;
SQL_TEXT SQL_ID PLAN_HASH_VALUE CHILD_NUMBER SQL_QUARANTINE AVOIDED_EXECUTIONS
------------------------------ ------------- --------------- ------------ ---------------------------------------- ------------------
SELECT burn_cpu (5) FROM dual gs59hr0xtjrf8 1388734953 0 SQL_QUARANTINE_8zpc9pwdmb8vr125daea2 1
SELECT burn_cpu (5) FROM dual gs59hr0xtjrf8 1388734953 1 SQL_QUARANTINE_8zpc9pwdmb8vr125daea2 0
SQL>
该DBA_SQL_QUARANTINE视图向我们显示了SQL隔离定义的详细信息。
COLUMN sql_text FORMAT A30
COLUMN name FORMAT A40
SELECT sql_text, name, plan_hash_value, enabled
FROM dba_sql_quarantine;
SQL_TEXT NAME PLAN_HASH_VALUE ENA
------------------------------ ---------------------------------------- --------------- ---
SELECT burn_cpu (5) FROM dual SQL_QUARANTINE_8zpc9pwdmb8vr125daea2 308129442 YES
SQL>
COLUMN sql_text FORMAT A30
COLUMN cpu_time FORMAT A10
COLUMN io_megabytes FORMAT A10
COLUMN io_requests FORMAT A10
COLUMN elapsed_time FORMAT A10
COLUMN io_logical FORMAT A10
SELECT sql_text, cpu_time, io_megabytes, io_requests, elapsed_time, io_logical
FROM dba_sql_quarantine;
SQL_TEXT CPU_TIME IO_MEGABYT IO_REQUEST ELAPSED_TI IO_LOGICAL
------------------------------ ---------- ---------- ---------- ---------- ----------
SELECT burn_cpu (5) FROM dual 60
SQL>
请注意,最后一个查询显示的阈值被打破,导致该查询被取消,因此首先被隔离。在这种情况下,CPU时间为60秒。
DBMS_SQLQ:SQL隔离管理
该DBMS_SQLQ软件包提供了用于管理SQL隔离的API。
我们可以基于SQL_ID或手动隔离语句SQL_TEXT。两种方法都接受一个PLAN_HASH_VALUE参数,这使我们能够隔离单个执行计划。如果未指定,则隔离该语句的所有执行计划。
以下是一些手动隔离语句的示例。
SET SERVEROUTPUT ON
-- Quarantine all execution plans for a SQL statement.
DECLARE
l_sql_quarantine VARCHAR2(100);
BEGIN
l_sql_quarantine := sys.DBMS_SQLQ.create_quarantine_by_sql_text(
sql_text => TO_CLOB('SELECT burn_cpu (5) FROM dual')
);
DBMS_OUTPUT.put_line('l_sql_quarantine=' || l_sql_quarantine);
END;
/
-- Quarantine a specific execution plan for a SQL statement.
DECLARE
l_sql_quarantine VARCHAR2(100);
BEGIN
l_sql_quarantine := sys.DBMS_SQLQ.create_quarantine_by_sql_text(
sql_text => TO_CLOB('SELECT burn_cpu (5) FROM dual'),
plan_hash_value => '1388734953'
);
DBMS_OUTPUT.put_line('l_sql_quarantine=' || l_sql_quarantine);
END;
/
-- Quarantine all execution plans for a SQL_ID.
DECLARE
l_sql_quarantine VARCHAR2(100);
BEGIN
l_sql_quarantine := sys.DBMS_SQLQ.create_quarantine_by_sql_id(
sql_id => 'gs59hr0xtjrf8'
);
DBMS_OUTPUT.put_line('l_sql_quarantine=' || l_sql_quarantine);
END;
/
-- Quarantine a specific execution plan for a SQL_ID.
DECLARE
l_sql_quarantine VARCHAR2(100);
BEGIN
l_sql_quarantine := sys.DBMS_SQLQ.create_quarantine_by_sql_id(
sql_id => 'gs59hr0xtjrf8',
plan_hash_value => '1388734953'
);
DBMS_OUTPUT.put_line('l_sql_quarantine=' || l_sql_quarantine);
END;
/
手动创建的隔离区的所有阈值均设置为ALWAYS。
SELECT sql_text, name, plan_hash_value, cpu_time, io_megabytes, io_requests, elapsed_time, io_logical
FROM dba_sql_quarantine;
SQL_TEXT NAME PLAN_HASH_VALUE CPU_TIME IO_MEGABYT IO_REQUEST ELAPSED_TI IO_LOGICAL
------------------------------ ---------------------------------------- --------------- ---------- ---------- ---------- ---------- ----------
SELECT burn_cpu (5) FROM dual SQL_QUARANTINE_8zpc9pwdmb8vr ALWAYS ALWAYS ALWAYS ALWAYS ALWAYS
SELECT burn_cpu (5) FROM dual SQL_QUARANTINE_8zpc9pwdmb8vr125daea2 308129442 60
SELECT burn_cpu (5) FROM dual SQL_QUARANTINE_8zpc9pwdmb8vr52c669e9 1388734953 ALWAYS ALWAYS ALWAYS ALWAYS ALWAYS
SQL>
该ALTER_QUARANTINE程序使我们可以更改阈值,使它们看起来更像自动生成的隔离区。我们可以使用该过程来更改以下参数。
- CPU_TIME
- ELAPSED_TIME
- IO_MEGABYTES
- IO_REQUESTS
- IO_LOGICAL
- ENABLED
- AUTOPURGE
这是CPU_TIME为手动创建的隔离区设置阈值的示例。
BEGIN
sys.DBMS_SQLQ.alter_quarantine(
quarantine_name => 'SQL_QUARANTINE_8zpc9pwdmb8vr',
parameter_name => 'CPU_TIME',
parameter_value => '60');
sys.DBMS_SQLQ.alter_quarantine(
quarantine_name => 'SQL_QUARANTINE_8zpc9pwdmb8vr52c669e9',
parameter_name => 'CPU_TIME',
parameter_value => '60');
END;
/
SELECT sql_text, plan_hash_value, cpu_time, io_megabytes, io_requests, elapsed_time, io_logical
FROM dba_sql_quarantine;
SQL_TEXT NAME PLAN_HASH_VALUE CPU_TIME IO_MEGABYT IO_REQUEST ELAPSED_TI IO_LOGICAL
------------------------------ ---------------------------------------- --------------- ---------- ---------- ---------- ---------- ----------
SELECT burn_cpu (5) FROM dual SQL_QUARANTINE_8zpc9pwdmb8vr 60
SELECT burn_cpu (5) FROM dual SQL_QUARANTINE_8zpc9pwdmb8vr125daea2 308129442 60
SELECT burn_cpu (5) FROM dual SQL_QUARANTINE_8zpc9pwdmb8vr52c669e9 1388734953 60
SQL>
删除隔离定义之前,您可能需要复制它们,如以下部分所述。
我们使用该DROP_QUARANTINE过程删除隔离区。
BEGIN
sys.DBMS_SQLQ.drop_quarantine('SQL_QUARANTINE_8zpc9pwdmb8vr');
sys.DBMS_SQLQ.drop_quarantine('SQL_QUARANTINE_8zpc9pwdmb8vr125daea2');
sys.DBMS_SQLQ.drop_quarantine('SQL_QUARANTINE_8zpc9pwdmb8vr52c669e9');
END;
/
传输隔离区定义
该DBMS_SQLQ软件包使我们能够在数据库之间传输SQL隔离定义。
我们使用该CREATE_STGTAB_QUARANTINE过程创建一个临时表。在STAGING_TABLE_OWNER和TABLESPACE_NAME参数默认为空,这意味着该表将在目前的方案中创建,并用于该模式的默认表空间。
BEGIN
sys.DBMS_SQLQ.create_stgtab_quarantine(staging_table_name => 'my_quarantine_defs',
staging_table_owner => 'testuser1',
tablespace_name => NULL);
END;
/
我们使用PACK_STGTAB_QUARANTINE函数将SQL隔离定义转移到登台表。默认情况下,它捕获所有定义。我们可以使用这个限制NAME,SQL_TEST并且ENABLED参数。在下面的示例中,我们使用默认值。我们可以省略这三个参数。
SET SERVEROUTPUT ON
DECLARE
l_number NUMBER;
BEGIN
l_number := sys.DBMS_SQLQ.pack_stgtab_quarantine (
staging_table_name => 'my_quarantine_defs',
staging_table_owner => 'testuser1',
name => '%',
sql_text => '%',
enabled => NULL);
DBMS_OUTPUT.put_line('l_number=' || l_number);
END;
/
l_number=3
PL/SQL procedure successfully completed.
SQL>
可以使用表级export和import将表转移到另一个数据库。
将表转移到新数据库后,可以使用以下UNPACK_STGTAB_QUARANTINE过程来加载定义。参数及其作用与PACK_STGTAB_QUARANTINE功能相似。
SET SERVEROUTPUT ON
DECLARE
l_number NUMBER;
BEGIN
l_number := sys.DBMS_SQLQ.unpack_stgtab_quarantine (
staging_table_name => 'my_quarantine_defs',
staging_table_owner => 'testuser1',
name => '%',
sql_text => '%',
enabled => NULL);
DBMS_OUTPUT.put_line('l_number=' || l_number);
END;
/
l_number=3
PL/SQL procedure successfully completed.
SQL>
文章来源:https://oracle-base.com/articles/19c/sql-quarantine-19c




