To understand this blog post you have to know what bind variable peeking is. You can found a very good explanation into this Kerry Osborne’s blog post.
So when dealing with performance issues linked to bind variable peeking you have to know:
- The peeked values (The ones that generate the execution plan)
- The passed values (The ones that have been passed to the sql statement)
Kerry Osborne helped us to retrieve the peeked values from v$sql_plan view into this blog post, but what about the passed values ? For those ones, Tanel Poder helped us to retrieve the passed values from v$sql_monitor into this blog post (This is reliable compare to v$sql_bind_capture)
Great ! So we know how to extract the peeked and the passed values. Another interesting point is that v$sql_monitor contains also the sql_exec_id field (see this blog post for more details about this field).
Here we are: It looks like that as of 11.2 we are able to retrieve the passed and peeked values per execution (If the statement is “monitored” which means CPU + I/O wait time >= 5 seconds per default (can be changed thanks to the _sqlmon_threshold hidden parameter).
But as your are dealing with performance issues related to bind variable peeking it is likely that the sql is monitored 😉
So let’s write the sql to do so, and let’s test it.
The sql script is the following:
SQL> !cat binds_peeked_passed.sql set linesi 200 pages 999 feed off verify off col bind_name format a20 col end_time format a19 col start_time format a19 col peeked format a20 col passed format a20 alter session set nls_date_format='YYYY/MM/DD HH24:MI:SS'; alter session set nls_timestamp_format='YYYY/MM/DD HH24:MI:SS'; select pee.sql_id, ash.starting_time, ash.end_time, (EXTRACT(HOUR FROM ash.run_time) * 3600 + EXTRACT(MINUTE FROM ash.run_time) * 60 + EXTRACT(SECOND FROM ash.run_time)) run_time_sec, pee.plan_hash_value, pee.bind_name, pee.bind_pos, pee.bind_data peeked, run_t.bind_data passed from ( select p.sql_id, p.sql_child_address, p.sql_exec_id, c.bind_name, c.bind_pos, c.bind_data from v$sql_monitor p, xmltable ( '/binds/bind' passing xmltype(p.binds_xml) columns bind_name varchar2(30) path '/bind/@name', bind_pos number path '/bind/@pos', bind_data varchar2(30) path '/bind' ) c where p.binds_xml is not null ) run_t , ( select p.sql_id, p.child_number, p.child_address, c.bind_name, c.bind_pos, p.plan_hash_value, case when c.bind_type = 1 then utl_raw.cast_to_varchar2(c.bind_data) when c.bind_type = 2 then to_char(utl_raw.cast_to_number(c.bind_data)) when c.bind_type = 96 then to_char(utl_raw.cast_to_varchar2(c.bind_data)) else 'Sorry: Not printable try with DBMS_XPLAN.DISPLAY_CURSOR' end bind_data from v$sql_plan p, xmltable ( '/*/peeked_binds/bind' passing xmltype(p.other_xml) columns bind_name varchar2(30) path '/bind/@nam', bind_pos number path '/bind/@pos', bind_type number path '/bind/@dty', bind_data raw(2000) path '/bind' ) c where p.other_xml is not null ) pee, ( select sql_id, sql_exec_id, max(sample_time - sql_exec_start) run_time, max(sample_time) end_time, sql_exec_start starting_time from v$active_session_history group by sql_id,sql_exec_id,sql_exec_start ) ash where pee.sql_id=run_t.sql_id and pee.sql_id=ash.sql_id and run_t.sql_exec_id=ash.sql_exec_id and pee.child_address=run_t.sql_child_address and pee.bind_name=run_t.bind_name and pee.bind_pos=run_t.bind_pos and pee.sql_id like nvl('&sql_id',pee.sql_id) order by 1,2,3,7 ;
Now let’s test it:
SQL> var my_owner varchar2(50) SQL> var my_date varchar2(30) SQL> var my_object_id number SQL> exec :my_owner :='BDT' PL/SQL procedure successfully completed. SQL> exec :my_date := '01-jan-2001' PL/SQL procedure successfully completed. SQL> exec :my_object_id :=1 PL/SQL procedure successfully completed. SQL> select /*+ MONITOR */ count(*),min(object_id),max(object_id) from bdt2 where owner=:my_owner and created > :my_date and object_id > :my_object_id; COUNT(*) MIN(OBJECT_ID) MAX(OBJECT_ID) ---------- -------------- -------------- 6974365 2 18233 SQL> @binds_peeked_passed.sql Enter value for sql_id: SQL_ID STARTING_TIME END_TIME RUN_TIME_SEC PLAN_HASH_VALUE BIND_NAME BIND_POS PEEKED PASSED ------------- ------------------- ------------------- ------------ --------------- -------------------- ---------- -------------------- -------------------- bu9367qrhq28t 2013/04/29 11:01:02 2013/04/29 11:01:07 5.678 1047781245 :MY_OWNER 1 BDT BDT bu9367qrhq28t 2013/04/29 11:01:02 2013/04/29 11:01:07 5.678 1047781245 :MY_DATE 2 01-jan-2001 01-jan-2001 bu9367qrhq28t 2013/04/29 11:01:02 2013/04/29 11:01:07 5.678 1047781245 :MY_OBJECT_ID 3 1 1
As this is the first execution then peeked values = passed values. Note that I used the “MONITOR” hint to force the sql to be monitored and then get an entry into v$sql_monitor.
Let’s put new passed values:
SQL> exec :my_date := '01-jan-2002' PL/SQL procedure successfully completed. SQL> exec :my_object_id :=100 PL/SQL procedure successfully completed. SQL> select /*+ MONITOR */ count(*),min(object_id),max(object_id) from bdt2 where owner=:my_owner and created > :my_date and object_id > :my_object_id; COUNT(*) MIN(OBJECT_ID) MAX(OBJECT_ID) ---------- -------------- -------------- 6923776 101 18233 SQL> @binds_peeked_passed.sql Enter value for sql_id: SQL_ID STARTING_TIME END_TIME RUN_TIME_SEC PLAN_HASH_VALUE BIND_NAME BIND_POS PEEKED PASSED ------------- ------------------- ------------------- ------------ --------------- -------------------- ---------- -------------------- -------------------- bu9367qrhq28t 2013/04/29 11:01:02 2013/04/29 11:01:07 5.678 1047781245 :MY_OWNER 1 BDT BDT bu9367qrhq28t 2013/04/29 11:01:02 2013/04/29 11:01:07 5.678 1047781245 :MY_DATE 2 01-jan-2001 01-jan-2001 bu9367qrhq28t 2013/04/29 11:01:02 2013/04/29 11:01:07 5.678 1047781245 :MY_OBJECT_ID 3 1 1 bu9367qrhq28t 2013/04/29 11:07:21 2013/04/29 11:07:25 4.139 1047781245 :MY_OWNER 1 BDT BDT bu9367qrhq28t 2013/04/29 11:07:21 2013/04/29 11:07:25 4.139 1047781245 :MY_DATE 2 01-jan-2001 01-jan-2002 bu9367qrhq28t 2013/04/29 11:07:21 2013/04/29 11:07:25 4.139 1047781245 :MY_OBJECT_ID 3 1 100
So as you can see, peeked values are the same and passed are not: bind variable peeking in action 😉
Conclusion:
We are able to retrieve peeked and passed values per execution.
Remarks:
- You need Diagnostic and tuning licenses pack to query v$active_session_history and v$sql_monitor.
- The query is not able to retrieve the DATE values (if any) from the v$sql_plan (check the code): This is because I don’t want to create a new function into the database. If you want to extract the DATE datatype then you could create the display_raw function (see Kerry Osborne’s blog post for this) and modify the sql.
- If you know how to extract DATE values from RAW type without creating new function please tell me so that i can update the code 😉
“If you know how to extract DATE values from RAW type without creating new function please tell me so that i can update the code”
http://marogel.wordpress.com/2013/04/29/utl_raw-cast_to_date/
just add
”
when c.bind_type = 12 then
to_char(
date’1-1-1′
+ NUMTOYMINTERVAL(100 * (to_number(substr(c.bind_data,1,2), ‘xx’) – 100) + to_number(substr(c.bind_data,3,2), ‘xx’) – 101, ‘year’)
+ NUMTOYMINTERVAL(to_number(substr(c.bind_data,5,2), ‘xx’)-1, ‘month’)
+ NUMTODSINTERVAL(to_number(substr(c.bind_data,7,2), ‘xx’)-1, ‘day’)
+ NUMTODSINTERVAL(to_number(substr(c.bind_data,9,2), ‘xx’) – 1, ‘hour’)
+ NUMTODSINTERVAL(to_number(substr(c.bind_data,11,2), ‘xx’) – 1, ‘minute’)
+ NUMTODSINTERVAL(to_number(substr(c.bind_data,13,2), ‘xx’) – 1, ‘second’)
)
”
to your query
How about DBMS_STATS.CONVERT_RAW_VALUE? (a procedure so needs a wrapper).
Or extract binds using DBMS_SQLTUNE.EXTRACT_BINDS (usual license caveats apply)
DBMS_STATS.CONVERT_RAW_VALUE is not an option as i don’t want to create a new function into the database.
As far DBMS_SQLTUNE.EXTRACT_BINDS, that’s a good idea but I am not sure it “always” display the “peeked” values: see Kyle’s comment here http://jonathanlewis.wordpress.com/2008/07/24/bind-capture/#comment-55126
Bertrand
You should be able to get the information other than from the high price packs. You can read out of the SGA if you have to.
Hi,
I am getting no output from above query , although SQL_ID is there in V$SQL and Insert statement is having lot of bind variable but bind value are not getting logged in oracle traces
Hello,
Then it is likely that the sql_id is not being monitored (not into v$sql_monitor). You can force the monitoring via a sql profile so that it will appear into the v$sql_monitor.
To force the monitoring you can use this script: http://www.oracle-guy.com/scripts/fm.sql
Bertrand
Useful script! A remark, if the passed variable length is higher than 30, output will be truncated (was truncated when I used it). I changed length of varchar on line 40.
Thx Bertrand.
Thanks for the pointer Vincent.