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 ;-)