Oracle histograms are often treated as devil of CBO as they cause bind variable peeking which is most of the times leads to quite nasty surprises when no one expects them. Common approach to avoid bind variable peeking is to remove histogram collection. However, it’s not true at all. Bind variable peeking can happen even without histogram and I am going to demonstrate with a simple test case. The only way I know so far is to use hidden parameter _optim_peek_user_binds (it’s still hidden even in 10g).
Originally, I found this issue in 9i and created a reproducible case. Later I also run in 10g and the result is the same. I will present here results as they are in 10.2.0.2 as there is more details in traces.
Sample data
Let’s create partitioned table and populate it with some data. I will simulate real life example when there are several partitions with majority of data and one partition with very few rows (like partition in the far future of a date partitioned table). Here is the script I used for that:
CREATE TABLE tp (
keycol NUMBER NOT NULL,
partcol NUMBER NOT NULL,
datacol VARCHAR2(1000) NOT NULL
)
PARTITION BY RANGE (partcol)
(
PARTITION p0 VALUES LESS THAN (1),
PARTITION p1 VALUES LESS THAN (2),
PARTITION p2 VALUES LESS THAN (3),
PARTITION p3 VALUES LESS THAN (4),
PARTITION pmax VALUES LESS THAN (MAXVALUE)
);
INSERT INTO tp
SELECT
TRUNC(ROWNUM/50),
MOD(TRUNC(ROWNUM/50),4),
RPAD(ROWNUM, 1000, '-')
FROM (SELECT null
FROM DUAL
CONNECT BY LEVEL <=10000
);
INSERT INTO tp (keycol, partcol, datacol)
VALUES (9000001, 99, 9000001);
INSERT INTO tp (keycol, partcol, datacol)
VALUES (9000002, 99, 9000003);
INSERT INTO tp (keycol, partcol, datacol)
VALUES (9000003, 99, 9000003);
CREATE INDEX tp_i ON tp (keycol);
EXEC DBMS_STATS.GATHER_TABLE_STATS(
ownname => user,
tabname => ‘TP’,
method_opt => ‘FOR ALL COLUMNS SIZE 1‘);
Note that I collected statistics without histograms (well, with one bucket) – FOR ALL COLUMNS SIZE 1. Index is global in that case but it can also be reproduced with local index as well.
Peeking
Now, let’s use the following statement with different values of bind variables:
SELECT datacol FROM tp WHERE keycol = :k AND partcol = :p;
Let’s enable 10046 trace on level 4 (to get binds) and run the following in SQL*Plus:
ALTER SYSTEM FLUSH SHARED_POOL; VAR k NUMBER; VAR p NUMBER; EXEC :k := 1;
EXEC :p := 1; SELECT datacol FROM tp WHERE keycol = :k AND partcol = :p; ALTER SYSTEM FLUSH SHARED_POOL; EXEC :k := 9000001;
EXEC :p := 99; SELECT datacol FROM tp WHERE keycol = :k AND partcol = :p;
When the first statement is closed we see the execution plan using index (formatted for readability):
STAT #1 id=1 cnt=50 pid=0 pos=1 obj=240565
op='TABLE ACCESS BY GLOBAL INDEX ROWID TP PARTITION:
ROW LOCATION ROW LOCATION
(cr=18 pr=0 pw=0 time=172 us)'
STAT #1 id=2 cnt=50 pid=1 pos=1 obj=240571
op='INDEX RANGE SCAN TP_I (cr=6 pr=0 pw=0 time=869 us)’
And the second time:
STAT #4 id=1 cnt=1 pid=0 pos=1 obj=0
op='PARTITION RANGE SINGLE PARTITION: KEY KEY
(cr=4 pr=0 pw=0 time=169 us)'
STAT #4 id=2 cnt=1 pid=1 pos=1 obj=240565
op='TABLE ACCESS FULL TP PARTITION: KEY KEY
(cr=4 pr=0 pw=0 time=128 us)’
What happened? Bind variables peeking without histogram?
10053
Let’s just quickly redo previous couple select with 10053 trace enabled. What we see for the first statement?
******************************************* Peeked values of the binds in SQL statement ******************************************* kkscoacd Bind#0 oacdty=02 mxl=22(22) mxlc=00 mal=00 scl=00 pre=00 oacflg=03 fl2=1000000 frm=00 csi=00 siz=48 off=0 kxsbbbfp=800003ffbfdd9fa0 bln=22 avl=02 flg=05 value=1 Bind#1 oacdty=02 mxl=22(22) mxlc=00 mal=00 scl=00 pre=00 oacflg=03 fl2=1000000 frm=00 csi=00 siz=0 off=24 kxsbbbfp=800003ffbfdd9fb8 bln=22 avl=02 flg=01 value=1
So Oracle does peek and in the first set of bind variable it chooses index access path:
Access Path: TableScan Cost: 86.84 Resp: 86.84 Degree: 0 Cost_io: 80.00 Cost_cpu: 3117218 Resp_io: 80.00 Resp_cpu: 3117218 Access Path: index (AllEqRange) Index: TP_I resc_io: 34.00 resc_cpu: 244864 ix_sel: 0.02 ix_sel_with_filters: 0.02 Cost: 10.36 Resp: 10.36 Degree: 1 Best:: AccessPath: IndexRange Index: TP_I Cost: 10.36 Degree: 1 Resp: 10.36 Card: 50.00 Bytes: 0
The second time Oracle peeks again on first hard parse:
******************************************* Peeked values of the binds in SQL statement ******************************************* kkscoacd Bind#0 oacdty=02 mxl=22(22) mxlc=00 mal=00 scl=00 pre=00 oacflg=03 fl2=1000000 frm=00 csi=00 siz=48 off=0 kxsbbbfp=800003ffbfc54200 bln=22 avl=05 flg=05 value=9000001 Bind#1 oacdty=02 mxl=22(22) mxlc=00 mal=00 scl=00 pre=00 oacflg=03 fl2=1000000 frm=00 csi=00 siz=0 off=24 kxsbbbfp=800003ffbfc54218 bln=22 avl=02 flg=01 value=99
And it produces full table scan of a single partition:
Access Path: TableScan Cost: 2.02 Resp: 2.02 Degree: 0 Cost_io: 2.00 Cost_cpu: 7851 Resp_io: 2.00 Resp_cpu: 7851 Access Path: index (AllEqRange) Index: TP_I resc_io: 543.00 resc_cpu: 3935310 ix_sel: 0.33333 ix_sel_with_filters: 0.33333 Cost: 165.49 Resp: 165.49 Degree: 1 Best:: AccessPath: TableScan Cost: 2.02 Degree: 1 Resp: 2.02 Card: 1.00 Bytes: 0
No Peeking
The only way to disable bind variables peeking is using hidden parameter. Try to rerun the test in the session with:
ALTER SESSION SET "_optim_peek_user_binds"=FALSE;
To our surprise looking at the 10053 trace we still see section:
******************************************* Peeked values of the binds in SQL statement *******************************************
But if you look carefully, there are no values. Looks like Oracle still keeps the section for consistency and, perhaps, to give some info to investigate cases when cursor is not shared because of bind type or size differences:
*******************************************
Peeked values of the binds in SQL statement
*******************************************
kkscoacd
Bind#0
oacdty=02 mxl=22(22) mxlc=00 mal=00 scl=00 pre=00
oacflg=03 fl2=1000000 frm=00 csi=00 siz=48 off=0
No bind buffers allocated
Bind#1
oacdty=02 mxl=22(22) mxlc=00 mal=00 scl=00 pre=00
oacflg=03 fl2=1000000 frm=00 csi=00 siz=0 off=24
No bind buffers allocated
In the same 10053 trace you can see that Oracle is choosing global statistics on the table now:
Table Stats:: Table: TP Alias: TP (Using composite stats) #Rows: 10003 #Blks: 289 AvgRowLen: 1006.00
Whereas previously Oracle narrowed it down to a partition:
Table Stats:: Table: TP Alias: TP Partition [1] #Rows: 2500 #Blks: 360 AvgRowLen: 1007.00
I hope Oracle will make _optim_peek_user_binds parameter public. I think we will at least open an SR for that.
So it’s getting quite late for me here… Have a nice peeking!
23 Responses to “Bind Variable Peeking with no Histograms”
- 1 Pingback on Oct 1st, 2007 at 2:11 pm
- 2 Pingback on Mar 12th, 2008 at 8:04 am


We have seen performances problems on some queries in our application when migrating the JDBC driver from 9.2.0.7 to 10.2.0.2 (both connected to the same instance of Oracle 9.2.0.7; the upgrade is required because we are going to use JDK 1.5 and the jdbc driver 9.2.0.7 is not certified for this version of the JDK).
I’m already working on these problems for one week now. I have tried many things, until I’ve discovered the 9i driver reacts well only when using bind variables while the 10g driver reacts bad either using bind variables or not.
Searching on the web for performance problems with bind variables, I’ve finally discovered this article (coming from http://oracleandy.blogspot.com/2006/05/bind-variable-peeking.html).
Then I have tried setting _optim_peek_user_binds to false with the 10g driver and now we are getting similar performance results as with the 9i driver. We can’t really explain why it’s like that just by changing the version of the driver, but to us it seems the 9i driver reacts like if this parameter was disabled by default, while the 10g driver reacts like if it was enabled by default.
Anyway, thank you very much for this article. We have updated our SR in metalink to tell Oracle about that. We have directed them to this article. We hope we will get an explanation soon.
Jean-Pol, thanks for your feedback.
This post is not much related to you problem I believe but I glad that as least part of it helped.
If your performance issues are related to changes in execution plans (perhaps, due to bind variable peeking) than I would suggest to run the problematic statement with trace event 10053 and see there why optimizer does it and particularly pay attention to any differences in the parameters.
I was saying this in my previous post:
“to us it seems the 9i driver reacts like if this parameter was disabled by default, while the 10g driver reacts like if it was enabled by default.”
That has been confirmed by our Oracle support. Moreover, it just depends on the version of the driver, no matter what’s the version of the database.
Oracle told us we can safely use _optim_peek_user_binds = FALSE in our production system without any danger or obscure side effect.
That has been confirmed by our Oracle support. Moreover, it just depends on the version of the driver, no matter what’s the version of the database.
This is weird. Thanks for this info.
Hi Alex
Alex, your example uses partitions, does this only happens with partitioned tables?
I have seen bind peeking happens with no histograms but I cannot find a explanation (9.2.0.7). The queries which I have seen this happen are usually not a plain query involving tables, they always involves JOIN.
Cheers
David
David,
The purpose of this example was to demonstrate exactly that bind variable peaking happens regardless presence of histograms. The most common impact of bind variable peaking is caused by histograms. I showed another possible impact coming from partitioning. It’s absolutely possible that Oracle CBO is able to change join cardinality evaluation using bind variable peaking. Perhaps, query rewrite happens and predicate is actually applied to another table that happens to have histograms. 10053 trace should uncover more.
Thanks for stopping by.
Alex
Hi Alex,
I Found your test most thoughtful. Just to add in with one simple observation: I went back and checked the manual for 10gR12 and it doesn’t differentiate bind peeking with histograms or without histograms. Just curious, is there another Oracle document that contratradicted this?
From Chapter 13 of the Performance tuning Guide:
“13.4.1.2 Peeking of User-Defined Bind Variables
The query optimizer peeks at the values of user-defined bind variables on the first invocation of a cursor. This feature lets the optimizer determine the selectivity of any WHERE clause condition, as well as if literals have been used instead of bind variables. On subsequent invocations of the cursor, no peeking takes place, and the cursor is shared, based on the standard cursor-sharing criteria, even if subsequent invocations use different bind values.
When bind variables are used in a statement, it is assumed that cursor sharing is intended and that different invocations are supposed to use the same execution plan. If different invocations of the cursor would significantly benefit from different execution plans, then bind variables may have been used inappropriately in the SQL statement. Bind peeking works for a specific set of clients, not all clients.”
Anyway, since you flushed the shared pool, I would expect it to peek again. Also, since partition elimination is involved for the partcol key, the amount of rows in each partition makes it likely that with only a few rows in the second example that it would full scan the partition. In other words, everything I saw was what I expected. In any case, I never knew about this bind parameter. Thanks for sharing. Please let me know if I’m missing anything here.
Jonas,
Shared pool flush is there exactly for that - to make sure we get a hard parse again. The case I demonstrated is for illustration only.
Imagine the first example in the “Peeking” sessions:
If the first statement (i.e. on a hard parse) uses binds targeting small partition - CBO produces execution plan with full partition scan. All consequent executions reusing that execution plan after soft parse will perform full partition scan that is a killer for large partitions (majority).
Nice demonstration. It’s good to be reminded that Oracle peeks on a hard parse and that even without histograms, different inputs can imply different plans.
Thanks Paulk. Even simpler demonstration would be with peeking on range scans and different ranges depending on maxval and minval but that was my original real-life case.
Hi Alex
You said that “Oracle histograms are often treated as devil of CBO as they cause bind variable peeking”.
But I don’t see any difference in CBO behavior depended of histograms existence (at least in 10g) when bind variables used.
Oracle performs bind variable peeking first time and used obtained plan further, regardless of histograms availability.
It’s my testcase here
rasskazov@BISDB> create table tst as select mod(rownum,2) num, ‘xx’ name from dual connect by level insert into tst (num, name) values(5, ‘zz’);
1 row created.
rasskazov@BISDB> commit;
Commit complete.
rasskazov@BISDB> create index idx_tst on tst (num);
Index created.
rasskazov@BISDB> BEGIN
2 SYS.DBMS_STATS.GATHER_TABLE_STATS (
3 OwnName => user
4 ,TabName => ‘TST’
5 ,Estimate_Percent => NULL
6 ,Method_Opt => ‘FOR COLUMNS SIZE 3 NUM’
7 ,Cascade => FALSE
8 ,No_Invalidate => FALSE);
9 END;
10 /
PL/SQL procedure successfully completed.
rasskazov@BISDB> set autotrace traceonly
rasskazov@BISDB> select * from tst where num = 1;
5000 rows selected.
Execution Plan
———————————————————-
Plan hash value: 2553322291
————————————————————————–
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
————————————————————————–
| 0 | SELECT STATEMENT | | 5000 | 25000 | 3 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| TST | 5000 | 25000 | 3 (0)| 00:00:01 |
————————————————————————–
Predicate Information (identified by operation id):
—————————————————
1 - filter(”NUM”=1)
Statistics
———————————————————-
1 recursive calls
0 db block gets
357 consistent gets
0 physical reads
0 redo size
37556 bytes sent via SQL*Net to client
2574 bytes received via SQL*Net from client
335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
5000 rows processed
rasskazov@BISDB> select * from tst where num = 5;
Execution Plan
———————————————————-
Plan hash value: 716636544
—————————————————————————————
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
—————————————————————————————
| 0 | SELECT STATEMENT | | 1 | 5 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| TST | 1 | 5 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | IDX_TST | 1 | | 1 (0)| 00:00:01 |
—————————————————————————————
Predicate Information (identified by operation id):
—————————————————
2 - access(”NUM”=5)
Statistics
———————————————————-
1 recursive calls
0 db block gets
3 consistent gets
30 physical reads
0 redo size
253 bytes sent via SQL*Net to client
243 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
rasskazov@BISDB> var zz number;
rasskazov@BISDB> exec :zz := 1;
PL/SQL procedure successfully completed.
rasskazov@BISDB> select * from tst where num = :zz;
5000 rows selected.
Execution Plan
———————————————————-
Plan hash value: 2553322291
————————————————————————–
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
————————————————————————–
| 0 | SELECT STATEMENT | | 3333 | 16665 | 3 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| TST | 3333 | 16665 | 3 (0)| 00:00:01 |
————————————————————————–
Predicate Information (identified by operation id):
—————————————————
1 - filter(”NUM”=TO_NUMBER(:ZZ))
Statistics
———————————————————-
1 recursive calls
0 db block gets
357 consistent gets
0 physical reads
0 redo size
37892 bytes sent via SQL*Net to client
2574 bytes received via SQL*Net from client
335 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
5000 rows processed
rasskazov@BISDB> exec :zz := 5;
PL/SQL procedure successfully completed.
rasskazov@BISDB> select * from tst where num = :zz;
Execution Plan
———————————————————-
Plan hash value: 2553322291
————————————————————————–
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
————————————————————————–
| 0 | SELECT STATEMENT | | 3333 | 16665 | 3 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| TST | 3333 | 16665 | 3 (0)| 00:00:01 |
————————————————————————–
Predicate Information (identified by operation id):
—————————————————
1 - filter(”NUM”=TO_NUMBER(:ZZ))
Statistics
———————————————————-
0 recursive calls
0 db block gets
23 consistent gets
0 physical reads
0 redo size
253 bytes sent via SQL*Net to client
243 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
rasskazov@BISDB> spool off
Vyacheslav,
First of all, thanks for reading the blog and commenting.
What you said is absolutely correct and if you re-read the post, you’d see — it’s what I tired to show with my example. Statement “Oracle histograms are often treated as devil of CBO as they cause bind variable peeking” is basically a myth I wanted to prove wrong. Histograms don’t cause bind variable peaking. They increase probability of unstable execution plans using bind variables.
Now, without looking at the details on your test case, you might be interested in looking at autotrace specific that causes it to provide execution plan that isn’t actually used during execution.
Also, I’m not sure how you wanted to show it in your example since it only showed case with histograms (‘FOR COLUMNS SIZE 3 NUM). Did I miss something?
Again, thanks for visiting my blog.
Alex,
In my example I showed exactly what you said. Two test cases: first with histograms and literals, and second with histograms and bind variables. Appropriate execution plan for both queries in first test case and only for one in second.
Thank you for link to Jonathan Lewis article.
Alex,
.
very useful and refreshing post.
The problem is that due to a bug 5082178 , bind variables are peekded, even if
“_optim_peek_user_binds”=FALSE.
I have had enormous problems with plan stability (on 10.2.0.2 ) with some SQLs,
and “_optim_peek_user_binds” was set to FALSE.
So, the problem should have been fixed in 10.2.0.4 , accourt to Oracle Corp.
(see http://www.dbatools.net/doc/bug10204.html). But first I want to see and then (eventually) believe it
Best Regards. Milen
Thanks for this Milen. The bug itself is not public but Metalink Note 5082178.8 says that In some situations bind peeking can occur when it should not.
Do you have more details about some situations?
Hello agian Alex,
whether a bind variable is peeked depends not only the presence of histograms, bit also on the value of the parameter CURSOR_SHARING.
CBO decides to peek (provided that “_optim_peek_user_binds”=TRUE & in absense of bugs -> see my latest post ) the deepending on whether the bind variable is considered “unsafe”.
A very good read are ML 296377.1 and 377847.1.
According to ML 296377.1 the “ultimate” proof (but not the reason why ! ;( ) that a bind variable is considered as “unsafe” is the oacfl2 attribute in a 10046 trace file. Short excerpt from the first ML note:
—————————————————
It is also possible to tell from 10046 trace (level 4/12 - BINDS) if a bind is considered to be unsafe
The flag oacfl2 in 9i and fl2 in 10g will show if a variable is unsafe.
BINDS #2:
bind 0: dty=2 mxl=22(04) mal=00 scl=00 pre=00 oacflg=10 oacfl2=500 size=24
offset=0
bfp=1036d6408 bln=22 avl=04 flg=09
value=16064
bind 1: dty=2 mxl=22(04) mal=00 scl=00 pre=00 oacflg=10 oacfl2=500 size=24
offset=0
bfp=1036d4340 bln=22 avl=04 flg=09
If you note oacfl2=500
#define UACFBLTR 0×00000100 /* Bind was generated by LiTeRal replacement */
#define UACFUNSL 0×00000200 /* UNSafe Literal */
#define UACFNDTL 0×00000400 /* Non-DaTa LiteRal */
—————————————————
HTH. Milen
Thanks for the references, Milen.
In my experience _optim_peek_user_binds parameter has helped pretty much every time and especially in cases with CURSOR_SHARING=SIMILAR.
There is another interesting Metalink Note () that references couple other bugs causing execution plan instability in the section “CONCLUSION” — bugs 4567767 and 5364143.
Thanks to all the contributors to this thread, which has been very useful in confirming and addressing our own similar problems.
I’m intrigued by the extract of Oracle documentation posted by Jonas:
>>
When bind variables are used in a statement, it is assumed that cursor sharing is intended and that different invocations are supposed to use the same execution plan. If different invocations of the cursor would significantly benefit from different execution plans, then bind variables may have been used inappropriately in the SQL statement. Bind peeking works for a specific set of clients, not all clients.”
>>
It seems to me Oracle have painted themselves into a logical corner: In a situation where it is not the case that “different invocations of the cursor would significantly benefit from different execution plans”, then there is no point in peeking the bind variable to determine the best path. So, if the one time that peeking the variable would be useful is the scenario where “bind variables may have been used inappropriately in the SQL statement”, why bother to implement bind variable peeking at all, ever?!
Did I miss something?
David
There are cases when you want to use bind variables (majority I’d say) and when you want to use literals (rather exceptional). Unfortunately, most developers don’t bother and either used binds everywhere or nowhere (there are good developers as well that do it properly - I’m luck to work with some of those).
BVP is a workaround for code where BV are used everywhere even where a literal would be more appropriate.
cursor_sharing=force/similar is workaround for the opposite - when binds are not used at all.
I think Oracle did good job disabling cursor sharing workaround by default (i.e. cursor_sharing=exact) but they did a mistake enabling BVP by default and, actually, not even exposing the underscore parameter to disable it.
Btw, when those two features are combined — you have the more “intriguing” effects (and bugs).
Why is it that cost of full tablescan is different between the 2 peeking ?
peek=1 :
Access Path: TableScan
Cost: 86.84 Resp: 86.84 Degree: 0
Cost_io: 80.00 Cost_cpu: 3117218
peek=9000001
Cost: 86.84 Resp: 86.84 Degree: 0
Cost_io: 80.00 Cost_cpu: 3117218
Resp_io: 80.00 Resp_cpu: 3117218
sorry I press too fast, the facts are :
peek=1 :
Access Path: TableScan
Cost: 86.84 Resp: 86.84 Degree: 0
Cost_io: 80.00 Cost_cpu: 3117218
Resp_io: 80.00 Resp_cpu: 3117218
peek=9000001
Access Path: TableScan
Cost: 2.02 Resp: 2.02 Degree: 0
Cost_io: 2.00 Cost_cpu: 7851
Resp_io: 2.00 Resp_cpu: 7851