Oracle SQL tuning tips

Oracle SQL Tuning Tips

consideration when writing an SQL statement is that it returns a correct result. The second is that it be the most efficient for a given situation.  You can use many different SQL statements to achieve the same result. It is often the case that only one statement will be the most efficient choice in a given situation.

Remember that processing SQL is a sequence of Parse (syntax check and object resolution), Execution (required reads and writes), and Fetch (row results retrieved, listed, sorted, and returned). SQL “tuning” consists, quite simply, of reducing one or more of them.

Note: generally Parse is the greatest time and resource hog. Parse overhead can be minimized by the use of Procedures, Functions, Packages, Views, etc.

Inadequate performance can have a significant cost impact on your business. A poor performing system and application can result in customer dissatisfaction, reduced productivity, and high costs. It is absolutely critical that the system’s performance is operating at its peak levels.

Following are some general tips that often increase SQL statement efficiency. Being general they may not apply to a particular scenario.

  • Tuning SQL should only be done after your code is working correctly. Be aware that there is an inevitable tug-of-war between writing efficient SQL and understandable SQL.
  • Ensure repeated SQL statements are writtenabsolutely identicallyto facilitate efficient reuse: re-parsing can often be avoided for each subsequent use of an identical statement.
    Writing best practices: all SQL verbs in upper-case i.e. SELECT; separate all words with a single space; all SQL verbs begin on a new line; SQL verbs aligned right or left within the initial verb; set and maintain a table alias standard; use table aliases and when a query involves more than one table prefix all column names with their aliases. Whatever you do, be consistent.
  • o Use bind variables: The values of bind variables do not need to be the same for two statements to be considered identical. Bind variables are not substituted until a statement has been successfully parsed.
  • o Use standard approach to table aliases. If two identical SQL statements vary because an identical table has two different aliases, then the SQL is different and will not be re-use or shared.
  • o Use table aliases and prefix all column names by their aliases when more than one table is involved in a query. This reduces parse time and prevents future syntax errors if someone adds a column to one of the tables with same name as a column in another table. (ORA-00918: COLUMN AMBIGUOUSLY DEFINED)
  • Code the query as simply as possible i.e. no unnecessary columns are selected, no unnecessary GROUP BY or ORDER BY.
    It is the same or faster to SELECT by actual column name(s). The larger the table the more likely the savings.
    Use:
    SELECT customer_id, last_name, first_name, street, city FROM customer;
    Rather than,

    SELECT * FROM customer;
  • Beware of WHERE clauses which do not use indexes at all. Even if there is an index over a column that is referenced by a WHERE clause included in this section, Oracle will ignore the index. All these WHERE clause can be re-written to use an index while returning the same values. In other words, Do not perform operations on database objects referenced in the WHERE clause:
    Use:
    SELECT client, date, amount FROM sales WHERE amount > 0;
    Rather than:
    SELECT client, date, amount FROM sales WHERE amount != 0;
    ———————————————
    Use:
    SELECT account_name, trans_date, amount FROM transaction WHERE account_name LIKE ‘CAPITAL%’;
    Rather than:
    SELECT account_name, trans_date, amount FROM transaction WHERE SUBSTR(account_name,1,7)=‘CAPITAL’;
    ———————————————
    Use:
    SELECT account_name, trans_date, amount FROM transaction WHERE amount > 0;
    Rather than:
    SELECT account_name, trans_date, amount FROM transaction WHERE amount NOT=0;
    ———————————————
    Use:
    SELECT account_name, trans_date, amount FROM transaction WHERE amount < 2000;
    Rather than:
    SELECT account_name, trans_date, amount FROM transaction WHERE amount + 3000 < 5000;
    ———————————————
    Use:
    SELECT account_name, trans_date, amount FROM transaction WHERE account_name = ‘AMEX’ AND account_type = ‘A’;
    Rather than:
    SELECT account_name, trans_date, amount FROM transaction WHERE account_name|| account_type=‘AMEXA’;
  • Don’t forget to tune views. Views are SELECT statements and can be tuned in just the same way as any other type of SLECT statement can be. All tuning applicable to any SQL statement are equally applicable to views.
  • Avoid including a HAVING clause in SELECT statements. The HAVING clause filters selected rows only after all rows have been fetched. Using a WHERE clause helps reduce overheads in sorting, summing, etc. HAVING clauses should only be used when columns with summary operations applied to them are restricted by the clause.
    Use:
    SELECT city FROM country WHERE city!= ‘Vancouver’ AND city!= ‘Toronto’; GROUP BY city;
    Rather than:
    SELECT city FROM country GROUP BY city HAVING city!= ‘Vancouver’ AND city!= ‘Toronto’;
  • Minimize the number of table lookups (subquery blocks) in queries, particularly if our statements include subquery SELECTs or multicolumn UPDATEs.
    Use:
    SELECT emp_name FROM emp WHERE (emp_cat, sal_range) = (SELECT MAX(category), MAX(sal_range) FROM emp_categories) AND emp_dept = 0020;
    Rather than:
    SELECT emp_name FROM emp WHERE emp_cat=(SELECT MAX(category) FROM emp_categories) AND emp_range = (SELECT MAX(sal_range) FROM emp_categories) AND emp_dept = 0020;
  • When writing a sub-query (a SELECT statement within the WHERE or HAVING clause of another SQL statement):
  • o Use a correlated (refers to at least one value from the outer query) sub-query when the return is relatively small and/or other criteria are efficient i.e. if the tables within the sub-query have efficient indexes.
  • o Use a non-correlated (does not refer to the outer query) sub-query when dealing with large tables from which you expect a large return (many rows) and/or if the tables within the sub-query do not have efficient indexes.
  • o Ensure that multiple sub-queries are in the most efficient order.
  • o Remember that rewriting a sub-query as a join can sometimes increase efficiency.
  • When doing multiple table joins consider the benefits/costs for each of EXISTS, IN, and table joins. Depending on your data one or another may be faster.
    Note: IN is usually the slowest.
    Note: When most of the filter criteria are in the sub-query IN may be more efficient; when most of the filter criteria are in the parent-query EXISTS may be more efficient.
    The following queries return the employee names from each department in department category ‘A’:

    SELECT emp_name FROM emp E WHERE EXISTS (SELECT ‘X’ FROM dept WHERE dept_no = E.dept_no AND dept_cat = ‘A’);
    ———————————————
    SELECT emp_name FROM emp E WHERE dept_no IN (SELECT dept_no FROM dept WHERE dept_no = E.dept_no AND dept_Cat = ‘A’);
    ———————————————
    SELECT emp_name FROM dept D, emp E WHERE E.dept_no = D.dept_no AND D.dept_cat = ‘A’;
  • Where possible use EXISTS rather than DISTINCT, to avoid full table scan as DISTINCT operator causes Oracle to fetch all rows satisfying the table join and then sort and filter out duplicate values. EXISTS is a faster alternative, because the Oracle optimizer realizes when the subquery has been satisfied once, there is no need to proceed further and the next matching row can be fetched.
    Use:
    SELECT S.id, S.description FROM small_table S WHERE EXISTS (SELECT NULL FROM big_table B WHERE B.id = S.id);
    Rather than:
    SELECT DISTINCT S.id, S.description FROM small_table S, big_table B
    WHERE S.id = B.id;
  • Where possible use a non-column expression (putting the column on one side of the operator and all the other values on the other). Non-column expressions are often processed earlier thereby speeding the query.
    Use:
    WHERE SALES < 1000/(1 + n);
    Rather than:
    WHERE SALES + (n * SALES) < 1000;
  • Where possible use UNION ALL rather than using UNION. The UNION clause forces all rows retuned by each portion of the UNION to be sorted and merged and duplicates to be filtered out before the first row is returned. A UNION ALL simply returns all rows including duplicates and does not have to perform any sort, merge or filter. If your tables are mutually exclusive(include no duplicate records), or you don’t care if duplicates are returned, the UNION ALL is much more efficient.
    USE:
    SELECT acct_num, balance_amt FROM debit_transactions WHERE tran_date = ’31-DEC-95′
    UNION ALL
    SELECT acct_num, balance_amt FROM credit_transactions WHERE tran_date = ’31-DE -95′;
    Rather than:
    SELECT acct_num, balance_amt FROM debit_transactions WHERE tran_date = ’31-DEC-95′
    UNION
    SELECT acct_num, balance_amt FROM credit_transactions WHERE tran_date = ’31-DE -95′;
  • Consider using DECODE to avoid having to scan the same rows repetitively or join the same table repetitively. Note, DECODE is not necessarily faster as it depends on your data and the complexity of the resulting query. Also, using DECODE requires you t change your code when new values are allowed in the field.
    USE:
    SELECT COUNT(DECODE(status,’Y’,’X’,NULL)) Y_count,
    COUNT(DECODE(status,’N’,’X’,NULL)) N_count
    FROM emp WHERE emp_name LIKE ‘SMITH%’;
    Raher than:
    SELECT COUNT(*) FROM emp WHERE status = ‘Y’ AND emp_name LIKE ‘SMITH%’;
    ———————
    SELECT COUNT(*) FROM emp WHERE status = ‘N’ AND emp_name LIKE ‘SMITH%’;
  • Oracle automatically performs simple column type conversions(or casting) when it compares columns of different types. Depending on the type of conversion, indexes may not be used. Make sure you declare your program variables as the same type as your Oracle columns, if the type is supported in the programming language you are using.
    Use:
    SELECT emp_no, emp_name, sal FROM emp WHERE emp_no = ‘123’;
    HERE if emp_no indexed numeric, then after implicit conversion query will be:
    SELECT emp_no, emp_name, sal FROM emp WHERE emp_no = TO_NUMBER(‘123’);

    Thus, index is used in this case.
    Don’t use:
    SELECT emp_no, emp_name, sal FROM emp WHERE emp_type = 123;
    HERE if emp_type is indexed varchar2, then after implicit conversion query will be:

    SELECT emp_no, emp_name, sal FROM emp WHERE TO_NUMBER(emp_type) = 123;
    Thus, index will not be used in this case.
  • The most efficient method for storing large binary objects, i.e. multimedia objects, is to place them in the file system and place a pointer in the DB.
  • B-Tree Indexes do not store entries for NULL, so IS NULL is not indexable, but IS NOT NULL is indexable and thus if a huge table contains very few not null values then you should go for B-Tree indexes. On the other hand bitmap indexes support IS NULL condition.
    SELECT * FROM big WHERE status IS NOT NULL; (Use B-tree index in this case)
  • Avoid using functions on indexed columns unless a function-based index is created; as it leads to full table scan even though index exists on the column.

Avoid using the following:

  • Boolean operators >, <, >=, <=, IS NULL, IS NOT NULL
  • NOT IN, !=
  • Like ‘%pattern’, not exists
  • Calculations on unindexed columns or (use union instead)
  • Having (use a WHERE clause instead when appropriate)

Do use the following:

  • Enable aliases to prefix all columns
  • Place indexed columns higher in the WHERE clause
  • Use SQL Joins instead of using sub-queries
  • Make the table with the least number of rows, the driving table, by making it first in the FROM clause

Other important points for SQL Tuning

  • Establish a tuning environment that reflects your production database
  • Establish performance expectations before you begin
  • Always Design and develop with performance in mind
  • Create Indexes to support selective WHERE clauses and join conditions
  • Use concatenated indexes where appropriate
  • Consider indexing more than you think you should, to avoid table lookups
  • Pick the best join method
  • Nested loops joins are best for indexed joins of subsets
  • Hash joins are usually the best choice for “big” joins
  • Pick the best join order
  • Pick the best “driving” table
  • Eliminate rows as early as possible in the join order
  • Use bind variables. Bind variables are key to application scalability
  • Use Oracle hints where appropriate
  • Compare performance between alternative syntax for your SQL statement
  • Consider utilizing PL/SQL to overcome difficult SQL tuning issues
  • Consider using third party tools to make the job of SQL tuning easier

Performing these steps is easy and provides a tremendous benefit and performance boost. Follow these simple steps and you can increase your system performance.

  1. Mike
    Mar 7th, 2016 at 14:00 | #1

    Thank YOU ! This is very useful !!

  2. Jun 25th, 2009 at 11:59 | #2

    Dear Elcorin,

    Which pic do you want to copy? Any technical blog image you can copy but do not copy the corporate site oratraining.com as they are part of our corporate identity.

    Regards,
    Blog Admin

  3. Jun 25th, 2009 at 04:46 | #3

    Hi,
    Can i take a one small pic from your site?
    Elcorin

    • Moeti
      Apr 16th, 2014 at 14:12 | #4

      This is useful

Leave a comment

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>