In my last post, I discussed the possible undesirable consequences of force and exact matching SQL profiles on statements with the same force matching signature. The question is how do you detect such profiles?
I have created three profiles on very similar SQL statements that only differ in the literal value of a predicate. One of them is force matching, the others are exact matching. The signature reported by DBA_SQL_PROFILES is the force matching signature for force matching profiles, and the exact matching signature for exact matching profiles.
select * from dba_sql_profiles;
NAME CATEGORY SIGNATURE SQL_TEXT CREATED
------------------------------ ---------- --------------------- -------------------------------------------------- ------------------------------
LAST_MODIFIED DESCRIPTION TYPE STATUS FOR TASK_ID TASK_EXEC_NAME TASK_OBJ_ID TASK_FND_ID TASK_REC_ID TASK_CON_DBID
------------------------------ -------------------- ------- -------- --- ---------- -------------------- ----------- ----------- ----------- -------------
my_sql_profile_force DEFAULT 11431056000319719221 16:09:33 01/08/2021
SELECT * FROM t WHERE a = 54
16:09:33 01/08/2021 MANUAL ENABLED YES
my_sql_profile_24 DEFAULT 12140764948557749245 16:09:33 01/08/2021
SELECT * FROM t
WHERE a = 24
16:09:33 01/08/2021 MANUAL ENABLED NO
my_sql_profile_42 DEFAULT 14843900676141266266 16:09:33 01/08/2021
SELECT * FROM t WHERE a = 42
16:09:33 01/08/2021 MANUAL ENABLED NO
In order to be able to compare the profiles, I need to calculate the force matching signature for the exact matching profiles using DBMS_SQLTUNE.SQLTEXT_TO_SIGNATURE. I can't use the Boolean constant TRUE parameter in SQL. Instead, I have used a PL/SQL function in a with clause.REM dup_sql_profiles1.sql
WITH function sig(p_sql_text CLOB, p_number INTEGER) RETURN NUMBER IS
l_sig NUMBER;
BEGIN
IF p_number > 0 THEN
l_sig := dbms_sqltune.sqltext_to_signature(p_sql_text,TRUE);
ELSIF p_number = 0 THEN
l_sig := dbms_sqltune.sqltext_to_signature(p_sql_text,FALSE);
END IF;
RETURN l_sig;
END;
x as (
select CASE WHEN force_matching = 'NO' THEN signature ELSE sig(sql_text, 0) END exact_sig
, CASE WHEN force_matching = 'YES' THEN signature ELSE sig(sql_text, 1) END force_sig
, p.*
from dba_sql_profiles p
where (status = 'ENABLED' or force_matching = 'NO')
), y as (
select x.*
, row_number() over (partition by category, force_sig order by force_matching desc, exact_sig nulls first) profile#
, count(*) over (partition by category, force_sig) num_profiles
from x
)
select profile#, num_profiles, force_sig, exact_sig, name, created, category, status, force_matching, sql_text
from y
where num_profiles > 1
order by force_sig, force_matching desc, exact_sig
/
We can see these three profiles are grouped together. The force matching signature calculated on the exact matching profiles is the same as the signature on the force matching profile. Now I can start to make some decisions about whether I should retain the exact matching profiles or remove them and just use the force matching profile.
Prof Num Force Matching Exact Matching
# Profs Signature Signature NAME CREATED CATEGORY STATUS FOR
---- ----- --------------------- --------------------- ------------------------------ ---------------------------- -------------------- -------- ---
SQL_TEXT
----------------------------------------------------------------------------------------------------------------------------------------------------
1 3 11431056000319719221 my_sql_profile_force 16:35:36 01/08/2021 DEFAULT ENABLED YES
SELECT * FROM t WHERE a = 54
2 3 12140764948557749245 my_sql_profile_24 16:35:36 01/08/2021 DEFAULT ENABLED NO
SELECT * FROM t
WHERE a = 24
3 3 14843900676141266266 my_sql_profile_42 16:35:36 01/08/2021 DEFAULT ENABLED NO
SELECT * FROM t WHERE a = 42
The SQL statements in this example are absurdly simple. In real life that is rarely the case. Sometimes it can be a struggle to see where two complex statements differ.In the next query, I compare enabled force matching SQL profiles to any exact matching profiles in the same category with the same force matching signature. The full query is on GitHub.
REM dup_sql_profiles2.sql
WITH function sig(p_sql_text CLOB, p_number INTEGER) RETURN NUMBER IS
…
END sig;
function norm(p_queryin CLOB) RETURN CLOB IS
…
END norm;
function str_diff(p_str1 CLOB, p_str2 CLOB) RETURN NUMBER IS
…
END str_diff;
x as (
select CASE WHEN force_matching = 'NO' THEN signature ELSE sig(sql_text, 0) END exact_sig
, CASE WHEN force_matching = 'YES' THEN signature ELSE sig(sql_text, 1) END force_sig
, p.*
from dba_sql_profiles p
), y as (
select f.force_matching, f.force_sig, f.name force_name, f.created force_created, f.status force_status
, e.force_matching exact_matching, e.exact_sig, e.name exact_name
, e.created exact_created, e.status exact_status, e.category
, norm(e.sql_text) esql_text, norm(f.sql_text) fsql_text
from x e
, x f
where f.force_matching = 'YES'
and e.force_matching = 'NO'
and e.force_sig = f.force_sig
and e.category = f.category
and e.name != f.name
and f.status = 'ENABLED'
), z as (
select y.*
, str_diff(fsql_Text, esql_text) diff_len
from y
)
select force_matching, force_Sig, force_name, force_created, force_status
, exact_matching, exact_sig, exact_name, exact_Created, exact_status
, substr(fsql_text,1,diff_len) common_text
, substr(fsql_text,diff_len+1) fdiff_text, substr(esql_text,diff_len+1) ediff_text
from z
order by force_sig
/
I have shown the common part of both statements, from the start to the first difference, and then also how the rest of each statement continues.It is not enough to simply compare two statements character by character. Both the force and exact matching signatures are "calculated on the normalized SQL text. The normalization includes the removal of white space and the uppercasing of all non-literal strings". However, neither the normalised SQL, nor the normalisation mechanism is exposed by Oracle. Therefore, in this query, I have included my own rudimentary normalisation function (based on an idea from AskTOM) that I apply first and a string comparison function. You can see that normalisation has eliminated the line feed in from the statement in my_sql_profile_24.
Now I can see my two exact matching profiles match my force matching profile. I can see the common part of the SQL up to the literal value, and the different parts of the text are just the literal value.
Force Matching Force Force Force Exact Matching Exact Exact Exact
FOR Signature Name Created Date Status EXA Signature Name Created Date Status
--- --------------------- ------------------------------ ---------------------------- -------- --- --------------------- ------------------------------ ---------------------------- --------
Common Text
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Force Text Exact Text
--------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------
YES 11431056000319719221 my_sql_profile_force 16:35:36 01/08/2021 ENABLED NO 12140764948557749245 my_sql_profile_24 16:35:36 01/08/2021 ENABLED
SELECT * FROM T WHERE A =
54 24
ENABLED NO 14843900676141266266 my_sql_profile_42 16:35:36 01/08/2021 ENABLED
SELECT * FROM T WHERE A =
54
Both the queries mentioned in this blog are available on GitHub.
No comments :
Post a Comment