In the past, when I created a SQL patch, I always specified a small number of hints. Last week, for the first time, I created one with more than 100 of them. Given their number, I didn’t want to specify them manually. Instead, my goal was to create a SQL patch that contained the outline associated with a cursor stored in shared pool. For that reason, I executed the following PL/SQL block:
DECLARE l_sql_id VARCHAR2(13) := '2q7d290pp5vmp'; l_name dba_sql_patches.name%TYPE := 'TEST'; l_hints CLOB; BEGIN dbms_lob.createtemporary(lob_loc => l_hints, cache => TRUE); FOR i IN (SELECT trim(extractValue(value(h),'.')) AS hint FROM v$sql_plan p, table(xmlsequence(extract(xmltype(p.other_xml),'/other_xml/outline_data/hint'))) h WHERE sql_id = l_sql_id AND id = 1) LOOP dbms_lob.append(dest_lob => l_hints, src_lob => chr(10) || i.hint); END LOOP; dbms_sqldiag.drop_sql_patch(name => l_name, ignore => TRUE); l_name := dbms_sqldiag.create_sql_patch(sql_id => l_sql_id, hint_text => l_hints, name => l_name); END;
Since the input parameter L_HINTS of the function CREATE_SQL_PATCH is a CLOB, passing many hints shouldn’t be a problem. Unfortunately, what I noticed is that despite the fact that the SQL patch was created, and later on used by the query optimizer, a dozen of hints stored into the table SQLOBJ$DATA were invalid. To check it, I executed the following query:
SELECT trim(extractValue(value(h),'.')) AS hint FROM sys.sqlobj$data od, sys.sqlobj$ so, table(xmlsequence(extract(xmltype(od.comp_data),'/outline_data/hint'))) h WHERE so.name = 'TEST' AND so.signature = od.signature AND so.category = od.category AND so.obj_type = od.obj_type AND so.plan_id = od.plan_id
Because of those invalid hints, the SQL patch didn’t work as expected.
While carefully checking the full list of hints stored in the table SYS.SQLOBJ$DATA, I noticed that the invalid hints weren’t stored at random positions. Instead, the number of characters between two invalid hints was always 500. Hence, I tried to pad the hints with some whitespaces to make sure that those “boundaries” weren’t in the middle of a hint. The amended PL/SQL block is the following (notice that the only change I did is that I replaced “i.hint” with “rpad(i.hint, 499, ‘ ‘)”):
DECLARE l_sql_id VARCHAR2(13) := '2q7d290pp5vmp'; l_name dba_sql_patches.name%TYPE := 'LONG_PARSE'; l_hints CLOB; BEGIN dbms_lob.createtemporary(lob_loc => l_hints, cache => TRUE); FOR i IN (SELECT trim(extractValue(value(h),'.')) AS hint FROM v$sql_plan p, table(xmlsequence(extract(xmltype(p.other_xml),'/other_xml/outline_data/hint'))) h WHERE sql_id = l_sql_id AND id = 1) LOOP dbms_lob.append(dest_lob => l_hints, src_lob => chr(10) || rpad(i.hint, 499, ' ')); END LOOP; dbms_sqldiag.drop_sql_patch(name => l_name, ignore => TRUE); l_name := dbms_sqldiag.create_sql_patch(sql_id => l_sql_id, hint_text => l_hints, name => l_name); END;
The SQL patch created with that PL/SQL block worked fine!
Then, I contacted Nigel Bayliss to know whether the bug was already a known one. Since it wasn’t, he created a new one (Bug 33824792 – I_CREATE_PATCH INCORRECTLY PROCESSES HINT_TEXT CLOB). Thank you, Nigel!
In summary, if you create a SQL patch with many hints, you have to format the list of hints in a way that every 500th character doesn’t fall in the middle of a hint. If you don’t do so, the database engine creates a SQL patch containing invalid hints.