Brown Bag Introduction to SQL Tuning Three essential concepts Introduction to SQL Tuning •How to speed up a slow query? • Find a better way to run the query • Cause the database to run the query your way Introduction to SQL Tuning •How does a database run a SQL query? • Join order • Join method • Access method Example Query SQL> 2 3 4 5 6 7 8 9 10 11 select sale_date, product_name, customer_name, amount from sales, products, customers where sales.product_number=products.product_number and sales.customer_number=customers.customer_number and sale_date between to_date('01/01/2012','MM/DD/YYYY') and to_date('01/31/2012','MM/DD/YYYY') and product_type = 'Cheese' and customer_state = 'FL'; SALE_DATE --------04-JAN-12 02-JAN-12 05-JAN-12 03-JAN-12 PRODUCT_NAME -----------Feta Chedder Feta Chedder CUSTOMER_NAME AMOUNT ----------------- ---------Sunshine State Co 300 Sunshine State Co 100 Green Valley Inc 400 Green Valley Inc 200 Join Order •Join Order = order in which tables in from clause are joined •Two row sources at a time •Row source: •Table •Result of join •View as tree – execution tree or plan Join Order – sales, products, customers join 2 join 1 sales customers products Join Order as Plan Execution Plan ---------------------------------------------------------0 SELECT STATEMENT 1 0 HASH JOIN 2 1 HASH JOIN 3 2 TABLE ACCESS (FULL) OF 'SALES' (TABLE) 4 2 TABLE ACCESS (FULL) OF 'PRODUCTS' (TABLE 5 1 TABLE ACCESS (FULL) OF 'CUSTOMERS' (TABLE) Bad Join Order – customers, products, sales join 2 join 1 customers sales products Cartesian Join – all products to all customers SQL> SQL> SQL> SQL> 2 3 4 5 6 -- joining products and customers -- cartesian join select product_name,customer_name from products, customers where product_type = 'Cheese' and customer_state = 'FL'; PRODUCT_NAME -----------Chedder Chedder Feta Feta CUSTOMER_NAME ----------------Sunshine State Co Green Valley Inc Sunshine State Co Green Valley Inc Plan with Cartesian Join Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Optimizer=ALL_ROWS 1 0 MERGE JOIN (CARTESIAN) 2 1 TABLE ACCESS (FULL) OF 'PRODUCTS' (TABLE) 3 1 BUFFER (SORT) 4 3 TABLE ACCESS (FULL) OF 'CUSTOMERS' (TABLE) Selectivity •Selectivity = percentage of rows accessed versus total rows •Use non-joining where clause predicates •sale_date, product_type, customer_state •Compare count of rows with and without non-joining predicates Count(*) to get selectivity -- # selected rows select count(*) from sales where sale_date between to_date('01/01/2012','MM/DD/YYYY') and to_date('01/31/2012','MM/DD/YYYY'); -- total #rows select count(*) from sales; SQL> 3 4 5 6 7 8 Selectivity of sub-tree select count(*) from sales, products where sales.product_number=products.product_number and sale_date between to_date('01/01/2012','MM/DD/YYYY') and to_date('01/31/2012','MM/DD/YYYY') and product_type = 'Cheese'; COUNT(*) ---------4 SQL> 2 3 4 select count(*) from sales, products where sales.product_number=products.product_number; COUNT(*) ---------4 Modifying the Join Order •Tables with selective predicates first •Gather Optimizer Statistics •Estimate Percent •Histogram on Column •Cardinality Hint •Leading Hint •Break Query into Pieces Gather Optimizer Statistics -- 1 - set preferences begin DBMS_STATS.SET_TABLE_PREFS(NULL,'SALES','ESTIMATE_PERCENT','10'); DBMS_STATS.SET_TABLE_PREFS(NULL,'SALES','METHOD_OPT', 'FOR COLUMNS SALE_DATE SIZE 254 PRODUCT_NUMBER SIZE 1 '|| 'CUSTOMER_NUMBER SIZE 1 AMOUNT SIZE 1'); end; / -- 2 - regather table stats with new preferences execute DBMS_STATS.GATHER_TABLE_STATS (NULL,'SALES'); Cardinality Hint SQL> 2 3 4 5 6 7 8 9 10 11 select /*+cardinality(sales 1) */ sale_date, product_name, customer_name, amount from sales, products, customers where sales.product_number=products.product_number and sales.customer_number=customers.customer_number and sale_date between to_date('01/01/2012','MM/DD/YYYY') and to_date('01/31/2012','MM/DD/YYYY') and product_type = 'Cheese' and customer_state = 'FL'; SALE_DATE --------04-JAN-12 02-JAN-12 05-JAN-12 03-JAN-12 PRODUCT_NAME -----------Feta Chedder Feta Chedder CUSTOMER_NAME AMOUNT ----------------- ---------Sunshine State Co 300 Sunshine State Co 100 Green Valley Inc 400 Green Valley Inc 200 Plan with Cardinality hint Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Optimizer=ALL_ROWS 1 0 HASH JOIN 2 1 HASH JOIN 3 2 TABLE ACCESS (FULL) OF 'SALES' (TABLE) 4 2 TABLE ACCESS (FULL) OF 'PRODUCTS' (TABLE 5 1 TABLE ACCESS (FULL) OF 'CUSTOMERS' (TABLE) Leading Hint SQL> 2 3 4 5 6 7 8 9 10 11 select /*+leading(sales) */ sale_date, product_name, customer_name, amount from sales, products, customers where sales.product_number=products.product_number and sales.customer_number=customers.customer_number and sale_date between to_date('01/01/2012','MM/DD/YYYY') and to_date('01/31/2012','MM/DD/YYYY') and product_type = 'Cheese' and customer_state = 'FL'; SALE_DATE --------04-JAN-12 02-JAN-12 05-JAN-12 03-JAN-12 PRODUCT_NAME -----------Feta Chedder Feta Chedder CUSTOMER_NAME AMOUNT ----------------- ---------Sunshine State Co 300 Sunshine State Co 100 Green Valley Inc 400 Green Valley Inc 200 Break Query Into Pieces SQL> 2 3 4 5 6 7 8 create global temporary table sales_product_results ( sale_date date, customer_number number, amount number, product_type varchar2(12), product_name varchar2(12) ) on commit preserve rows; Table created. Break Query Into Pieces SQL> 2 3 4 5 6 7 8 9 10 11 12 13 14 15 insert /*+append */ into sales_product_results select sale_date, customer_number, amount, product_type, product_name from sales, products where sales.product_number=products.product_number and sale_date between to_date('01/01/2012','MM/DD/YYYY') and to_date('01/31/2012','MM/DD/YYYY') and product_type = 'Cheese'; 4 rows created. Break Query Into Pieces SQL> 2 3 4 5 6 select sale_date, product_name, customer_name, amount from sales_product_results spr, customers c where spr.customer_number=c.customer_number and c.customer_state = 'FL'; SALE_DATE --------02-JAN-12 03-JAN-12 04-JAN-12 05-JAN-12 PRODUCT_NAME -----------Chedder Chedder Feta Feta CUSTOMER_NAME AMOUNT ----------------- ---------Sunshine State Co 100 Green Valley Inc 200 Sunshine State Co 300 Green Valley Inc 400 Join Methods •Join Method = way that data from two sources is joined •Nested Loops •Small number of rows in first table •Unique index on second large table •Hash Join •Smaller or equal number of rows in first table •No index required Join Method – Nested Loops Execution Plan -----------------------------------------------------------------0 SELECT STATEMENT Optimizer=ALL_ROWS 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (TABLE) 2 1 NESTED LOOPS 3 2 NESTED LOOPS 4 3 TABLE ACCESS (FULL) OF 'SALES' (TABLE) 5 3 TABLE ACCESS (BY INDEX ROWID) OF 'PRODUCTS' 6 5 INDEX (RANGE SCAN) OF 'PRODUCTS_INDEX' (INDEX) 7 2 INDEX (RANGE SCAN) OF 'CUSTOMERS_INDEX' (INDEX) Join Method – Hash Join Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Optimizer=ALL_ROWS 1 0 HASH JOIN 2 1 HASH JOIN 3 2 TABLE ACCESS (FULL) OF 'SALES' (TABLE) 4 2 TABLE ACCESS (FULL) OF 'PRODUCTS' 5 1 TABLE ACCESS (FULL) OF 'CUSTOMERS' (TABLE) Modifying the Join Method •Hints •use_hash •use_nl •Add Index •Hash_area_size parameter Join Methods Hints /*+ use_hash(products) use_nl(customers) */ Join Methods Indexes create index products_index on products(product_number); create index customers_index on customers(customer_number); Join Methods Hash_Area_Size NAME -----------------------------------hash_area_size sort_area_size workarea_size_policy TYPE ----------integer integer string VALUE --------100000000 100000000 MANUAL Access Methods •Access method = way that data is retrieved from table •Index scan – small number of rows accessed •Full scan – larger number of rows accessed Modifying the Access Method •Set Initialization Parameter •optimizer_index_caching •optimizer_index_cost_adj •db_file_multiblock_read_count •Set Parallel Degree > 1 •Hints •Full •Index Set Initialization Parameter alter system set optimizer_index_cost_adj=1000 scope=both sid='*'; Set Parallel Degree alter table sales parallel 8; Full Scan and Index Hints /*+ full(sales) index(customers) index(products) */ Conclusion • Use count queries to determine selective parts of where clause • Modify the join order, join methods, and access methods using • Optimizer statistics • Hints • Initialization parameters • Breaking the query into pieces • Parallel degree • Indexes • Compare elapsed time of query with new plan to original Check For Improved Elapsed Time SQL> set timing on SQL> SQL> select … … removed for clarity … SALE_DATE --------02-JAN-12 03-JAN-12 04-JAN-12 05-JAN-12 PRODUCT_NAME -----------Chedder Chedder Feta Feta Elapsed: 00:00:00.00 CUSTOMER_NAME AMOUNT ----------------- ---------Sunshine State Co 100 Green Valley Inc 200 Sunshine State Co 300 Green Valley Inc 400 Further Reading • Oracle Database Concepts • Chapter 7 SQL • Oracle Database Performance Tuning Guide • Chapter 11 The Query Optimizer • Chapter 19 Using Optimizer Hints • Oracle Database Reference • Chapter 1 Initialization Parameters • Oracle Database PL/SQL Packages and Types Reference • Chapter 141 DBMS_STATS • Cost-Based Oracle Fundamentals - Jonathan Lewis • http://www.bobbydurrettdba.com/resources/