Saat Anda menggunakan variabel bind, Oracle terpaksa menggunakan pemangkasan partisi dinamis alih-alih pemangkasan partisi statis . Hasilnya adalah Oracle tidak mengetahui pada waktu parse partisi mana yang akan diakses, karena ini berubah berdasarkan variabel input Anda.
Ini berarti bahwa ketika menggunakan nilai literal (bukan variabel bind), kita tahu partisi mana yang akan diakses oleh indeks lokal Anda. Oleh karena itu count stopkey
dapat diterapkan ke output indeks sebelum kita memangkas partisi.
Saat menggunakan variabel bind, partition range iterator
harus mencari tahu partisi mana yang Anda akses. Kemudian memiliki pemeriksaan untuk memastikan bahwa variabel pertama Anda di antara operasi benar-benar memiliki nilai yang lebih rendah daripada yang kedua (filter
operasi dalam rencana kedua).
Ini dapat dengan mudah direproduksi, seperti yang ditunjukkan oleh kasus uji berikut:
create table tab (
x date,
y integer,
filler varchar2(100)
) partition by range(x) (
partition p1 values less than (date'2013-01-01'),
partition p2 values less than (date'2013-02-01'),
partition p3 values less than (date'2013-03-01'),
partition p4 values less than (date'2013-04-01'),
partition p5 values less than (date'2013-05-01'),
partition p6 values less than (date'2013-06-01')
);
insert into tab (x, y)
select add_months(trunc(sysdate, 'y'), mod(rownum, 5)), rownum, dbms_random.string('x', 50)
from dual
connect by level <= 1000;
create index i on tab(x desc, y desc) local;
exec dbms_stats.gather_table_stats(user, 'tab', cascade => true);
explain plan for
SELECT * FROM (
SELECT rowid FROM tab
where x between date'2013-01-01' and date'2013-02-02'
and y between 50 and 100
order by x desc, y desc
)
where rownum <= 5;
SELECT * FROM table(dbms_xplan.display(null, null, 'BASIC +ROWS +PARTITION'));
--------------------------------------------------------------------
| Id | Operation | Name | Rows | Pstart| Pstop |
--------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | |
| 1 | COUNT STOPKEY | | | | |
| 2 | VIEW | | 1 | | |
| 3 | SORT ORDER BY STOPKEY | | 1 | | |
| 4 | PARTITION RANGE ITERATOR| | 1 | 2 | 3 |
| 5 | COUNT STOPKEY | | | | |
| 6 | INDEX RANGE SCAN | I | 1 | 2 | 3 |
--------------------------------------------------------------------
explain plan for
SELECT * FROM (
SELECT rowid FROM tab
where x between to_date(:st, 'dd/mm/yyyy') and to_date(:en, 'dd/mm/yyyy')
and y between :a and :b
order by x desc, y desc
)
where rownum <= 5;
SELECT * FROM table(dbms_xplan.display(null, null, 'BASIC +ROWS +PARTITION'));
---------------------------------------------------------------------
| Id | Operation | Name | Rows | Pstart| Pstop |
---------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | |
| 1 | COUNT STOPKEY | | | | |
| 2 | VIEW | | 1 | | |
| 3 | SORT ORDER BY STOPKEY | | 1 | | |
| 4 | FILTER | | | | |
| 5 | PARTITION RANGE ITERATOR| | 1 | KEY | KEY |
| 6 | INDEX RANGE SCAN | I | 1 | KEY | KEY |
---------------------------------------------------------------------
Seperti pada contoh Anda, kueri kedua hanya dapat memfilter partisi ke key
pada waktu parse, bukan partisi persis seperti pada contoh pertama.
Ini adalah salah satu kasus langka di mana nilai literal dapat memberikan kinerja yang lebih baik daripada variabel pengikat. Anda harus menyelidiki apakah ini kemungkinan bagi Anda.
Akhirnya, Anda mengatakan Anda ingin 20 baris dari setiap partisi. Permintaan Anda sebagai stan tidak akan melakukan ini, itu hanya akan mengembalikan Anda 20 baris pertama sesuai dengan pemesanan Anda. Untuk 20 baris/partisi, Anda perlu melakukan sesuatu seperti ini:
select rd from (
select rowid rd,
row_number() over (partition by trx_id order by create_ts desc) rn
from OUT_SMS
where TRX_ID between ? and ?
and CREATE_TS between ? and ?
order by CREATE_TS DESC, TRX_ID DESC
) where rn <= 20
PERBARUI
Alasan Anda tidak mendapatkan count stopkey
ada hubungannya dengan filter
operasi di baris 4 dari rencana "buruk". Anda dapat melihat ini lebih jelas jika mengulangi contoh di atas, tetapi tanpa partisi.
Ini memberi Anda paket berikut:
----------------------------------------
| Id | Operation | Name |
----------------------------------------
| 0 | SELECT STATEMENT | |
|* 1 | COUNT STOPKEY | |
| 2 | VIEW | |
|* 3 | SORT ORDER BY STOPKEY| |
|* 4 | TABLE ACCESS FULL | TAB |
----------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(ROWNUM<=5)
3 - filter(ROWNUM<=5)
4 - filter("X">=TO_DATE(' 2013-01-01 00:00:00', 'syyyy-mm-dd
hh24:mi:ss') AND "X"<=TO_DATE(' 2013-02-02 00:00:00', 'syyyy-mm-dd
hh24:mi:ss') AND "Y">=50 AND "Y"<=100)
----------------------------------------
| Id | Operation | Name |
----------------------------------------
| 0 | SELECT STATEMENT | |
|* 1 | COUNT STOPKEY | |
| 2 | VIEW | |
|* 3 | SORT ORDER BY STOPKEY| |
|* 4 | FILTER | |
|* 5 | TABLE ACCESS FULL | TAB |
----------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(ROWNUM<=5)
3 - filter(ROWNUM<=5)
4 - filter(TO_NUMBER(:A)<=TO_NUMBER(:B) AND
TO_DATE(:ST,'dd/mm/yyyy')<=TO_DATE(:EN,'dd/mm/yyyy'))
5 - filter("Y">=TO_NUMBER(:A) AND "Y"<=TO_NUMBER(:B) AND
"X">=TO_DATE(:ST,'dd/mm/yyyy') AND "X"<=TO_DATE(:EN,'dd/mm/yyyy'))
Seperti yang Anda lihat, ada filter
tambahan operasi ketika Anda menggunakan variabel ikat yang muncul sebelum sort order by stopkey
. Ini terjadi setelah mengakses index. Ini memeriksa bahwa nilai untuk variabel akan memungkinkan data dikembalikan (variabel pertama di antara Anda sebenarnya memiliki nilai yang lebih rendah daripada yang kedua). Ini tidak diperlukan saat menggunakan literal karena pengoptimal sudah mengetahui bahwa 50 kurang dari 100 (dalam hal ini). Namun tidak diketahui apakah :a lebih kecil dari :b pada waktu parse.
Mengapa tepatnya ini saya tidak tahu. Ini bisa jadi merupakan desain yang disengaja oleh Oracle - tidak ada gunanya melakukan pemeriksaan stopkey jika nilai yang ditetapkan untuk variabel menghasilkan baris nol - atau hanya kelalaian.