-- -- Set vs Row Processing - Which is better? -- -- Author: Craig A. Shallahamer, craig@orapub.com -- 29-May-2012 -- -- Use at your own risk, not OraPub's -- set echo on feedback on verify on -- Disable experiment killing stuff... set tab off -- disable AWR collection or your numbers can be strangely skewed. execute dbms_workload_repository.modify_snapshot_settings(interval => 0); -- disable resource manager to ensure Oracle CPU activity is not throttled down. If this occurs -- you will see a quantum related wait event. alter system set resource_manager_plan = ''; show parameter resource_manager_plan set serveroutput on def maxCustomers=500 -- number of customers def totOrders=1000000 -- total number of orders; e.g., 1000 def orderSets=10 -- number of order sets; e.g., 3 : 300 orders, 600 orders, 900 orders def maxSamples=12 -- number of timed samples for each order set def chill=4 -- seconds to pause before gathering timed samples prompt . prompt Creating objects prompt . create or replace function op_get_interval_s(i_intrvl interval day to second) return number is begin return extract(day from i_intrvl) * 86400 + extract(hour from i_intrvl) * 3600 + extract(minute from i_intrvl) * 60 + extract(second from i_intrvl); exception when others then begin return null; end; end; / create or replace function op_get_normal(avg_in number, stdev_in number) return number is begin declare sample_value number; z number; -- standardized value m number := avg_in ; -- average s number := stdev_in ; -- standard deviation x number; -- un-standardized value begin z := dbms_random.normal; x := z * s + m ; return x ; end; end; / create or replace function op_get_cust_id(min_in number, avg_in number, max_in number) return number is begin declare cust_id_v number; begin loop cust_id_v := trunc(op_get_normal(avg_in,max_in/3)); exit when (cust_id_v < max_in) and (cust_id_v > min_in) ; end loop; return cust_id_v ; end; end; / drop table op_results; create table op_results ( number_of_orders number, sample_no number, proc_type varchar2(5), -- row or set time_proc_s number, create_date date, order_sum_amount number ); drop table op_customer; create table op_customer ( id number, name varchar2(200), bogus1 varchar2(200), bogus2 varchar2(200), status varchar2(5) ); create index op_customer_id on op_customer (id,status); drop sequence op_seq; create sequence op_seq; declare i_v number; cust_id_v number; cust_name_v varchar2(200); cust_bogus1_v varchar2(200); cust_bogus2_v varchar2(200); cust_status_v varchar2(5); begin for i_v in 1..&maxCustomers loop cust_id_v := i_v ; cust_name_v := dbms_random.string('A', trunc(dbms_random.value(100,200))); cust_bogus1_v := dbms_random.string('A', trunc(dbms_random.value(100,200))); cust_bogus2_v := dbms_random.string('A', trunc(dbms_random.value(100,200))); cust_status_v := trunc(dbms_random.value(1,3)); insert into op_customer values (cust_id_v, cust_name_v, cust_bogus1_v, cust_bogus2_v, cust_status_v); end loop; end; / commit; drop table op_interim; create table op_interim ( cust_id number, cust_status varchar2(5), cust_name varchar2(200), count_orders number, sum_amount number ); drop table op_order; create table op_order ( cust_id number, id number, description varchar2(200), amount number, bogus1 varchar2(200) ); create index op_order_pk on op_order (cust_id,id); prompt . prompt Run experiment prompt . declare type cv_typ is ref cursor; cust_cur cv_typ; order_cur cv_typ; order_sets_v number; order_id_v number; number_of_orders_v number; cust_id_v number; order_id_v number; description_v varchar2(200); amount_v number; bogus1_v varchar2(200); sum_amount_v number; count_orders_v number; cust_status_v varchar2(5); cust_name_v varchar2(200); samples_no_v number; time_t0_v timestamp; time_t1_v timestamp; elapsed_s_v number; cust_cur_id_v number; cust_cur_status_v varchar2(5); cust_cur_name_v varchar2(200); order_cur_id_v number; order_cur_cust_id_v number; order_cur_amount_v number; order_count_v number; order_sum_amount_v number; order_sum_check_v number; begin for order_sets_v in 1..&orderSets loop -- -- Load data into orders table (this can take awhile...) -- execute immediate('truncate table op_order'); number_of_orders_v := order_sets_v * trunc( &totOrders / &orderSets ) ; for order_id_v in 1..number_of_orders_v loop cust_id_v := op_get_cust_id(1,&maxCustomers/2,&maxCustomers); description_v := dbms_random.string('A', trunc(dbms_random.value(100,200))); amount_v := dbms_random.value(250,1000000); bogus1_v := dbms_random.string('A', trunc(dbms_random.value(100,200))); insert into op_order values (cust_id_v, order_id_v, description_v, amount_v, bogus1_v); commit write batch nowait; end loop; commit; execute immediate('truncate table op_interim'); dbms_lock.sleep(&chill); -- -- Collect experimental samples -- for sample_no_v in 1..&maxSamples loop -- -- set processing -- execute immediate('truncate table op_interim'); dbms_lock.sleep(&chill); select current_timestamp into time_t0_v from dual; insert into op_interim select c.id, c.status, c.name, count(o.amount), sum(o.amount) from op_customer c, op_order o where c.status = '1' and c.id = o.cust_id (+) group by c.id, c.status, c.name; commit; select current_timestamp into time_t1_v from dual; select op_get_interval_s(time_t1_v-time_t0_v) into elapsed_s_v from dual; select sum(sum_amount) into order_sum_check_v from op_interim; insert into op_results values (number_of_orders_v, sample_no_v, 'set', elapsed_s_v, sysdate, order_sum_check_v); commit; end loop; for sample_no_v in 1..&maxSamples loop -- -- row processing -- execute immediate('truncate table op_interim'); dbms_lock.sleep(&chill); select current_timestamp into time_t0_v from dual; order_sum_check_v := 0; open cust_cur for select id, status, name from op_customer order by id; loop fetch cust_cur into cust_cur_id_v, cust_cur_status_v, cust_cur_name_v ; exit when cust_cur%NOTFOUND; order_count_v := 0 ; order_sum_amount_v := 0 ; open order_cur for select id, cust_id, amount from op_order order by cust_id, id; loop fetch order_cur into order_cur_id_v, order_cur_cust_id_v, order_cur_amount_v ; exit when order_cur%NOTFOUND; if (cust_cur_status_v = '1') AND (cust_cur_id_v = order_cur_cust_id_v) then order_count_v := order_count_v + 1 ; order_sum_amount_v := order_sum_amount_v + order_cur_amount_v ; order_sum_check_v := order_sum_check_v + order_cur_amount_v ; end if; end loop; close order_cur; insert into op_interim values (cust_cur_id_v, cust_cur_status_v, cust_cur_name_v, order_count_v, order_sum_amount_v); end loop; close cust_cur; select current_timestamp into time_t1_v from dual; select op_get_interval_s(time_t1_v-time_t0_v) into elapsed_s_v from dual; insert into op_results values (number_of_orders_v, sample_no_v, 'row', elapsed_s_v, sysdate, order_sum_check_v); commit; end loop; end loop; -- order_sets_v loop end; / ---------------------------- -- Reporting SQL ---------------------------- set tab off -- Full Data Dump select * from op_results; col time_proc_s format 999990.0000 select number_of_orders, sample_no, proc_type, time_proc_s, to_char(create_date,'DD-Mon-YYYY HH24:MI:SS') date_stamp, order_sum_amount from op_results order by 5; -- Q: Is there a sig elapsed time diff between set and row processing for each number of orders? -- I graphedI chose the what is cleary (by visual observation) the most closest call. -- select time_proc_s||',' from op_results where number_of_orders=100000 and proc_type='set'; select time_proc_s||',' from op_results where number_of_orders=100000 and proc_type='row'; -- Q: Is there is a sig diff between throughput; set vs row -- col np noprint col avg_throughput format 999999990.0000 select number_of_orders np, '{'||number_of_orders||',' , avg(number_of_orders/time_proc_s), '},' avg_throughput from op_results where proc_type = 'set' group by number_of_orders order by number_of_orders; select number_of_orders np, '{'||number_of_orders||',' , avg(number_of_orders/time_proc_s), '},' avg_throughput from op_results where proc_type = 'row' group by number_of_orders order by number_of_orders; -- Q: Does processing for set processing scale better compared to row processing? col np noprint select number_of_orders np,'{'||number_of_orders||',' , avg(time_proc_s), '},' y from op_results where proc_type = 'set' group by number_of_orders, '{'||number_of_orders||',' order by number_of_orders; select number_of_orders np,'{'||number_of_orders||',' , avg(time_proc_s), '},' y from op_results where proc_type = 'row' group by number_of_orders, '{'||number_of_orders||',' order by number_of_orders; -- END