Scan Oracle-DB for excessive execution of SQL with literals instead of bind variables

Using literals instead of bind variables in SQLs may have several drawbacks:
  • Performance slows down because of:
    • worse cache hit ratio
    • higher parse effort
  • Growing SQL-area in shared pool pushes other major memory objects outside, especially DB-cache
  • Each executed SQL has to be hard parsed separately
  • Risk of SQL-injection may exist (depending from source of literal)

Methods to find heavy executed SQLs with missing bind variables

1. Scan active session history for different SQL-ID but same execution plan

WITH Ret AS (SELECT :Consider_Days_Back Days FROM DUAL)
SELECT x.SQL_Plan_Hash_Value, x.Different_SQL_IDs, x.Last_Used_SQL_ID, u.UserName, x.First_Occurrence, x.Last_Occurrence, x.Elapsed_Secs
FROM   (
      SELECT h.SQL_Plan_Hash_Value,
             COUNT(DISTINCT h.SQL_ID) Different_SQL_IDs,
             MAX(h.SQL_ID) KEEP (DENSE_RANK LAST ORDER BY h.Sample_Time) Last_Used_SQL_ID,
             User_ID,
             MIN(h.Sample_Time) First_Occurrence,
             MAX(h.Sample_Time) Last_Occurrence,
             SUM(CASE WHEN h.Sample=1 OR d.Min_Sample_Time IS NULL OR h.Sample_Time < d.Min_Sample_Time THEN h.Sample END) Elapsed_Secs   /* dont count twice in SGA and AWR */
      FROM   (SELECT Inst_ID Instance_Number, Sample_Time, SQL_Plan_Hash_Value, SQL_ID, 1 Sample, User_ID,
                     MIN(Sample_Time) OVER (PARTITION BY SQL_Plan_Hash_Value) Delimiter
              FROM   gv$Active_Session_History
              CROSS JOIN Ret
              WHERE  Sample_Time > SYSDATE-Ret.Days
              UNION ALL
              SELECT Instance_Number, Sample_Time, SQL_Plan_Hash_Value, SQL_ID, 10 Sample, User_ID,NULL Delimiter
              FROM   DBA_Hist_Active_Sess_History
              CROSS JOIN Ret
              WHERE  Sample_Time > SYSDATE-Ret.Days
             ) h
      LEFT OUTER JOIN (SELECT Inst_ID Instance_Number, SQL_Plan_Hash_Value, MIN(Sample_Time) Min_Sample_Time   /* limit Values in AWR-table from SGA*/
                       FROM   gv$Active_Session_History
                       GROUP BY  Inst_ID, SQL_Plan_Hash_Value
                      ) d ON d.Instance_Number = h.Instance_Number AND d.SQL_Plan_Hash_Value = h.SQL_Plan_Hash_Value
      WHERE  h.SQL_Plan_Hash_Value != 0
      GROUP BY h.SQL_Plan_Hash_Value, h.User_ID
     ) x
JOIN   DBA_Users u ON u.User_ID = x.User_ID
WHERE Different_SQL_IDs > :Min_Different_SQLs
ORDER BY Different_SQL_IDs DESC
;   

2. Scan SGA for different SQL-ID but same execution plan

SELECT a.Inst_ID                                                         "Instance",
       a.Plan_Hash_Value                                                 "Plan hash value",
       a.Parsing_Schema_Name                                             "Parsing schema",
       COUNT(*)                                                          "No. of entries in gv$SQLArea",
       COUNT(DISTINCT a.SQL_ID)                                          "No. of different SQL-IDs",
       MIN(a.Last_Active_Time)                                           "Oldest active time",
       MAX(a.Last_Active_Time)                                           "Youngest active time",
       MAX(a.SQL_ID) KEEP (DENSE_RANK LAST ORDER BY Last_Active_Time)    "SQL_ID",
       ROUND(SUM(a.Sharable_Mem)  /(1024*1024), 2)                       "Sharable memory (MB)",
       ROUND(SUM(a.Persistent_Mem)/(1024*1024), 2)                       "Persistent memory (MB)",
       ROUND(SUM(a.Runtime_Mem)   /(1024*1024), 2)                       "Runtime memory (MB)",
       SUBSTR(MAX(a.SQL_Text) KEEP (DENSE_RANK LAST ORDER BY Last_Active_Time), 1, 400)  "SQL text"
FROM   gv$SQLArea a
WHERE  a.Plan_Hash_Value != 0
GROUP BY a.Inst_ID, a.Plan_Hash_Value, a.Parsing_Schema_Name
HAVING COUNT(*) > :Min_Different_SQLs
ORDER BY 4 DESC
;

3. Scan the SGA for different SQLs with same leading characters in statement

WITH Len AS (SELECT :Num_Chars_to_Compare Substr_Len FROM DUAL)
SELECT g.*, s.SQL_Text "Beispiel SQL-Text"
FROM   (
       SELECT COUNT(*) Variationen, Inst_ID, MIN(Parsing_Schema_Name) UserName, COUNT(DISTINCT Parsing_Schema_Name) Anzahl_User,
              SUBSTR(s.SQL_Text, 1, Len.Substr_Len) SubSQL_Text,
              ROUND(SUM(Sharable_Mem+Persistent_Mem+Runtime_Mem)/(1024*1024),3) "Memory (MB)",
              MIN(s.SQL_ID) SQL_ID,
              MIN(TO_DATE(s.First_Load_Time, 'YYYY-MM-DD/HH24:MI:SS')) Min_First_Load,
              MIN(Last_Load_Time) Min_Last_Load,
              MAX(Last_Load_Time) Max_Last_Load,
              MAX(Last_Active_Time) Max_Last_Active,
              MIN(Parsing_Schema_Name) Parsing_Schema_Name,
              COUNT(DISTINCT Parsing_Schema_Name) "Different pars. schema names"
       FROM   gv$SQLArea s, Len
       GROUP BY Inst_ID, SUBSTR(s.SQL_Text, 1, Len.Substr_Len)
       HAVING COUNT(*) > 10
      ) g
JOIN gv$SQLArea s ON s.Inst_ID = g.Inst_ID AND s.SQL_ID = g.SQL_ID
ORDER BY "Memory (MB)" DESC NULLS LAST
;

Panorama offers methods to quantify the impact of missing bind variables more comfortable and drill down into SQLs:

1. Check your system for SQL with missing bind variables sorted by number of occurrences

You can scan your system for SQLs with missing bind variables by two ways at menu „Spec. additions“ / „Dragnet investigation“, point 4.1 until 4.3

  • List SQLs with multiple SQL-ID but same execution plan from SGA and also from Active Session History
  • List SQLs with multiple SQL-ID but same fist n characters

2. Quantify memory consumption due to excessive SQLs flooding SQL-area

One obstacle is: If you don’t set limits for db_cache_size in initialization than excessive execution of SQLs with different literals instead of bind variables leads to growing SQL-area in shared pool, thereby reducing the buffer cache.
This may dramatically reduce your cache hit rate and enlarge execution times all over your database.

Common sizes for shared pool are between 0.5 and 2 GB for larger database systems, the greater part of SGA should be available for buffer cache.
If you consider a SQL-area that is much greater than the buffer cache, this is often a signal for massive problem with missing bind variables.

 Even if you drive your database with cursor_sharing=FORCE to fix the missing bind variables issue, than full SQL text before translation by cursor_sharing=FORCE is stored in KHLH0-area of shared pool and may also consume gigabytes.

You can recognize your memory dividing by Panorama at menu „SGA/PGA-Details“ / „SGA components“ like shown here:


You can find more about Panorama at http://rammpeter.github.io/panorama.html

Comments

Popular posts from this blog

Common pitfalls using SQL*Net via Firewalls

Oracle-DB: How to check for appropriate sequence caching

Oracle-DB: Link between audit trail and active session history