On one of the databases I’m looking after (22.214.171.124, Solaris, non-RAC), several different INSERT statements (all into tablespaces with manually managed segments) suffer from occasional hiccups. The symptoms are always the same: in one of the sessions, the INSERT gets stuck doing lots of single-block I/O against one of the indexes on the inserted table, and if other sessions are running similar INSERTs, they hang on enq: TX – index contention. The situation can last just a few seconds, but sometimes it’s much longer than this (several minutes), in which case the impact on the application is quite serious.
In my previous post I mentioned method R as probably the most efficient approach to SQL optimization. However, it is important to focus on correct metrics for it to work correctly.
Consider this example (once again, the query is still running, so the only reliable diagnostic tool at our disposal is SQL real-time monitor):
Performance tuning is all about time. You measure the time it takes for a certain process to complete, and then you search for ways to reduce this time to improve end-users experience and/or increase the application productivity. But minimizing time is not enough — it’s important to minimize the correct time metric. A typical mistake in database performance optimization to optimize DB time instead of the duration experienced by the end user. If this happens, this can easily result in a situation when DB time is reduced significantly, but the process is still taking almost as long as before, the SLA is still breached, bosses and users are increasingly frustrated.
In this post, I will give two real-life examples of cases when the difference between DB time and elapsed time was the key to understanding the problem.
SQL performance can degrade for many reasons, some of most common are:
– plan changes
– data skewness
– low caching efficiency
– data growth
All these factors are relatively well known. A somewhat less common, although not exceptionally rare scenario, is read consistency overhead due to concurrent DML against queried tables. Because of being less common, this scenario is often overlooked, which leads to false diagnoses (and eventually to “fixes” that can do more harm than good).
Occasionally I encounter a situation when I need to affect a part of the plan that corresponds to a view, e.g.:
select * from ( select v.x, x.y from v ) q where q.x = 1
Such situations are resolved using global hints. Oracle offers two ways to specify a global hint: via a query block identifier (system generated or user defined) or via view aliases. System-generated query block identifiers can be obtained via dbms_xplan.display with ALL or ALIAS option (they have the form SEL$n, where n appears to be same as the depth, e.g. in our case 1 corresponds to the main query, 2 to the inline view, 3 to the view V inside that inline view) or defined by the user via qb_name hint.
In general, tuning analytic functions (and more generally, all sort operations) is rather difficult. While for most poorly performing queries it’s relatively straightforward to gain some improvements by applying “eliminate early” principle one way or another, for slow sort operations it’s rarely applicable. Usually options are limiting to rewriting a query without analytics (e.g. using self-joins or correlated subqueries to achieve the same goal) or manually resizing the workarea to reduce/eliminate the use of disk. Recently, however, I had a case where I managed to obtain an excellent performance gain using a different technique that I would like to share in this post.
The original query was selecting about 100 columns using the LAG function on one of the columns in the WHERE clause, but in my test case I’ll both simplify and generalize the situation. Let’s create a table with a sequential id, three filtering columns x, y and z, and 20 sufficiently lengthy columns.
In AWR analysis, what appears to be the root cause of the issue, can easily turn out to be just a symptom. Last week, Rajat sent me an AWR report which is a perfect illustration of this (thanks Rajat), I posted the key sections from this report below (sorry for less than perfect formatting — I had to manually re-format the HTML version of the report into text).
WORKLOAD REPOSITORY report for DB Name DB Id Instance Inst num Release RAC Host DSS 37220993 dss 1 10.2.0.4.0 NO dssdbnz Snap Id Snap Time Sessions Cursors/Session Begin Snap: 18471 12-Oct-12 08:30:28 131 1.5 End Snap: 18477 12-Oct-12 14:30:24 108 1.8 Elapsed: 359.93 (mins) DB Time: 25,730.14 (mins) Load Profile Per Second Per Transaction Redo size: 325,282.85 103,923.02 Logical reads: 33,390.52 10,667.77 Block changes: 1,307.95 417.87 Physical reads: 1,927.33 615.75 Physical writes: 244.65 78.16 User calls: 391.34 125.03 Parses: 68.14 21.77 Hard parses: 3.33 1.06 Sorts: 47.86 15.29 Logons: 3.15 1.01 Executes: 234.32 74.86 Transactions: 3.13 % Blocks changed per Read: 3.92 Recursive Call %: 61.11 Rollback per transaction %: 24.71 Rows per Sort: 3325.52 Top 5 Timed Events Event Waits Time(s) Avg Wait(ms) % Total Call Time Wait Class free buffer waits 10,726,838 344,377 32 22.3 Configuration db file sequential read 6,122,262 335,366 55 21.7 User I/O db file scattered read 3,597,607 305,576 85 19.8 User I/O CPU time 161,491 10.5 read by other session 2,572,875 156,821 61 10.2 User I/O Operating System Statistics Statistic Total AVG_BUSY_TIME 2,093,109 AVG_IDLE_TIME 63,212 AVG_IOWAIT_TIME 18,463 AVG_SYS_TIME 87,749 AVG_USER_TIME 2,004,722 BUSY_TIME 16,749,988 IDLE_TIME 510,692 IOWAIT_TIME 152,594 SYS_TIME 707,137 USER_TIME 16,042,851 LOAD 4 OS_CPU_WAIT_TIME ############### RSRC_MGR_CPU_WAIT_TIME 0 VM_IN_BYTES 5,503,492,096 VM_OUT_BYTES 2,054,414,336 PHYSICAL_MEMORY_BYTES 34,288,209,920 NUM_CPUS 8 NUM_CPU_SOCKETS 8