Topics • Common ways to rewrite SQL to make it perform beFer and more consistently • How to easily idenHfy & test your SQL • How and when to index – AddiHons or modiﬁcaHons to provide best soluHon – Best choice of columns and in what order – Trade-‐oﬀs for determining the "best" index
Name your SQL SELECT /* kmtest */ … FROM tab … SELECT /*+ qb_name (sql42) */ … FROM tab …Comments stored with full SQL in the SQL_TEXT column in v$sql, v$sqltext, dba_hist_sqltext. QBLOCK_NAME stored in v$sql_plan, v$sql_plan_staHsHcs_all, dba_hist_sql_plan.
@fsx kmtest!!!col sql_id new_value r_sqlid!col child_number new_value r_childno!!!SELECT /* NOVIEW */ ! ! ! sql_id, child_number ...!FROM gv$sql s!WHERE sql_text like %&&1%!AND sql_text not like %NOVIEW%!AND sql_text not like BEGIN :sql_%!ORDER BY 1 ;!
@dplan!SELECT *!FROM table(dbms_xplan.display_cursor (! ! ! SQL_ID => &r_sqlid, ! ! ! CURSOR_CHILD_NO => NVL(&r_childno,0), ! ! ! FORMAT => ALLSTATS LAST)) !WHERE &r_sqlid IS NOT NULL ;!@rsm!SELECT dbms_sqltune.report_sql_monitor(! ! ! SQL_ID => &r_sqlid,! ! ! TYPE => HTML) !FROM dual!WHERE &r_sqlid IS NOT NULL ;!
How do you know when refactoring SQL is the best opHon?
A poor execuHon plan is consistently developed.
Why You Should Refactor • You know your stuﬀ best (or you should) • Always ﬁlter early • Deﬁnes your expectaHons • K.I.S.S. • The opHmizer might not be able to
This AND tab1.col1 = tab2.col1 (+) AND tab2.col2 = <condition>Becomes AND tab1.col1 = tab2.col1 AND tab2.col2 = <condition>Because the condiHon would be null for the outer joined row, so the predicate could never be true.
Look for repeated use of same tables and predicates.
SELECT rite.event_name, count(*) FROM riffs.rf_order ro, riffs.rf_order_item roi, riffs.rf_item_transaction rit, riffs.rf_item_transaction_event rite WHERE ro.is_test = 0 AND ro.order_id = roi.order_id AND roi.order_item_id = rit.order_item_id AND roi.order_id = rit.order_id AND rit.transaction_id = rite.transaction_id AND (rite.event_name >AUTHORIZED OR rite.event_name <AUTHORIZED) GROUP BY rite.event_nameUNION ALLSELECT TRANSACTION_INITIATED, count(*) FROM(SELECT count(*) FROM riffs.rf_order ro, riffs.rf_order_item roi, riffs.rf_item_transaction rit, riffs.rf_item_transaction_event rite WHERE ro.is_test = 0 AND ro.order_id = roi.order_id AND roi.order_item_id = rit.order_item_id AND roi.order_id = rit.order_id AND rit.transaction_id = rite.transaction_id AND rite.event_name = AUTHORIZED GROUP BY substr(rit.TRANSACTION_ID,1,INSTR(rit.TRANSACTION_ID,_)-1))
Look for simple predicates ORed with other predicates in ranges.
This col1 > <condition> OR col2 > <condition>Becomes col1 > <condition> UNION / UNION ALL AND col2 > <condition>Because a row could not be rejected when one predicate is false without checking the other predicates.
Look for DISTINCT/UNION to remove duplicates. Consider using IN or EXISTS instead.
Simple View Merging is transformed intoMerged automaHcally as it is deemed “always beFer” for the opHmizer to work with direct joins.
Complex View Merging is transformed into “Complex” due to GROUP BY. CVM can also be done when using DISTINCT or outer join.
Filter Push-‐Down is transformed intoPurpose: To push outer query predicates into view to perform earlier ﬁltering.
Predicate Move-‐Around is transformed intoPurpose: To move inexpensive predicates into view query blocks to perform earlier ﬁltering. Can generate ﬁlter predicates based on transiHvity or funcHonal dependencies.
Join FactorizaHon is transformed intoCombines branches of UNION / UNION ALL that join a common table in order to reduce # of accesses to that table.
Understanding how the opHmizer transforms queries helps you write beFer SQL and understand execuHon plans.
For many years, inadequate indexing has been the most common cause of performance disappointments. – Tapio Lahdenmäki
Indexing Problems • Indexes that do not have suﬃcient columns to support all predicates • Not enough indexes present – Numerous single-‐column but few mulH-‐column • Indexes with the right columns but in the wrong order
Inadequate Index SELECT !cust_id, cust_first_name!FROM ! !customers! !WHERE ! !cust_last_name = Ruddy!AND ! ! !cust_city = Ede!ORDER BY cust_first_name ;! Index present on CUST_LAST_NAME, CUST_FIRST_NAME # rows in table = 55,500
Note the number of rows that are thrown away in step 1 (79).
Index on CUST_LAST_NAME, CUST_CITY No throwaway Index on CUST_CITY, CUST_LAST_NAME, CUST_FIRST_NAME, CUST_ID No throwaway, no sort
Index Design Strategies Columns from all equality predicates in any order Add columns in the order used in ORDER BY Add all remaining columns from column list in any order • VolaHle columns at end (reduces impact on updates)
Range predicates are usually placed ater 1 and 2 star columns. Range predicates mean 3-‐star indexes arent possible. The 2nd star is usually sacriﬁced in preference of a more selecHve index.
An index that contains all the columns referenced in the WHERE clause is a semi-fat index. If no semi-‐fat index exists, this is a warning ﬂag for possible performance issues.
There will always be trade-‐oﬀs. Test alternaHves or use "worst case" formula to esHmate response Hmes.
The key to determining an ideal index The index should provide adequate enough screening to minimize table accesses.
Recap • Look for common anH-‐paFerns in SQL • Gather enough diagnosHc data to know where the problem originates • Learn what the opHmizer expects (and give it what it wants!) • Think about your indexing strategy • Design indexes for opHmal coverage to limit table accesses