After nine years and nine months of running the database that hosts asktom, I've retired.
Not from answering questions, but rather from being the DBA and semi-SA for the machine that was asktom.oracle.com.
Asktom.oracle.com is now synonymous with APEX.oracle.com - Joel Kallman facilitated the move (here is the story on that). So, I'm now running in a database with thousands of other applications. And - it seems a bit faster than my old hardware (which was held together with some glue and duct-tape - the drives were failing left and right...).
For some stories on APEX and the ability to handle a pretty large load - I suggest you glance at:
But now, for the first time in years, I won't be afraid to check asktom when I land and get off of the plane. If something goes wrong - the data center guys will fix it :)
And yes, the intermittent "some IE users get an error on asktom" issue was found and fixed.
Today a reader posted a question I like at our Method R website. It's about the story I tell in the article called, "Can you explain Method R so even my boss could understand it?" The story is about sending your son on a shopping trip, and it takes him too long to complete the errand. The point is that an excellent way to fix any kind of performance problem is to profile the response time for the properly chosen task, which is the basis for Method R (both the method and the company).
Here is the profile that details where the boy's time went during his errand:
--Duration---
Subtask minutes % Executions
------------------ ------- ---- ----------
Talk with friends 37 62% 3
Choose item 10 17% 5
Walk to/from store 8 13% 2
Pay cashier 5 8% 1
------------------ ------- ---- ----------
Total 60 100%
I went on to describe that the big leverage in this profile is the elimination of the subtask called "Talk with friends," which will reduce response time by 62%.
The interesting question that a reader posted is this:
Not sure this is always the right approach. For example, lets imagine the son has to pick 50 items
Talk 3 times 37 minutes
Choose item 50 times 45 minutes
Walk 2 times 8 minutes
Pay 1 time 5 minutes
Working on "choose item" is maybe not the right thing to do...
Let's explore it. Here's what the profile would look like if this were to happen:
--Duration---
Subtask minutes % Executions
------- ------- ---- ----------
Choose 45 47% 50
Talk 37 39% 3
Walk 8 8% 2
Pay 5 5% 1
------- ------- ---- ----------
Total 95 100%
The point of the inquiry is this:
The right answer in this case, too, is to begin with eliminating Talk from the profile. That's because, even though it's not ranked at the very top of the profile, Talk is completely unnecessary to the real goal (grocery shopping). It's a time-waster that simply shouldn't be in the profile. At all. But with Cary's method of addressing the profile from the top downward, you would instead focus on the "Choose" line, which is the wrong thing.
In chapters 1 through 4 of our book about Method R for Oracle, I explained the method much more thoroughly than I did in the very brief article. In my brevity, I skipped past an important point. Here's a summary of the Method R steps for diagnosing and resolving performance problems using a profile:
Here's a narrative of executing the steps of the diagnostic phase, one at a time, upon the new profile, which—again—is this:
--Duration---
Subtask minutes % Executions
------- ------- ---- ----------
Choose 45 47% 50
Talk 37 39% 3
Walk 8 8% 2
Pay 5 5% 1
------- ------- ---- ----------
Total 95 100%
Now you're done assessing the effects of (1) execution elimination and (2) latency reduction for each line in the profile. That ends the diagnostic phase of the method. The next step is the resolution phase: to determine which of these candidate solutions is the best. Given the analysis I've walked you through, I'd rank the candidate solutions in this order:
That's it.
So the point I want to highlight is this:
I'm not saying you should stick to the top line of your profile until you've absolutely conquered it.
It is important to pass completely through your profile to construct your set of candidate solutions. Then, on a separate pass, you evaluate those candidate solutions to determine which ones you want to implement, and in what order. That first full pass is key. You have to do it for Method R to be reliable for solving any performance problem.
Two things
One, here is something new I learned today. I wasn't aware of that, that is a nice "stealth" feature :)
The other thing - I have a podcast on 11g Release 2 "things" posted on oracle.com if you are interested....
I was reviewing a performance issue with a client recently. Problem is that increased global cache waits causing application slowdown affecting few critical business functions. Using one of my script gc_traffic.sql > and graphing the results with Excel spreadsheet, it is established that there is a marked increase in GC traffic today compared to week earlier. Similar jobs runs every day and so comparing two week days is sufficient to show the GC traffic increase. Graph is between total blocks and AWR snap time in 30 minutes interval. [Click the picture below to review the graph clearly.]
Identifying the object creating this increased GC traffic is essential to identify root cause. We were able to quickly determine that this increase in GC traffic was localized around few SQL statements using ADDM and AWR reports. We decided to focus on one SQL with an obvious increase in elapsed time compared to prior week. So, first question asked, is there a change in the plan? plan_hash_value was reviewed and quickly determined that there is no change in the plan_hash_value.
Little bit of history, there were few changes performed by the vendor over the weekend as a part of few bug fixes. Vendor’s argument was that since there is no change to the plan_hash_value, SQL access plan did not change and so, this can’t be due to vendor changes. My Client’s argument was that there were no changes to the environment and problem started after the application changes.
There are many different things that can go wrong without changes to the execution plan. We can ignore those conditions for now since (a) there has been no changes to the environment (b) no visible changes to the data (c) no error message and average CR recv time is consistent with prior weeks. Well, Let’s cut to the chase. It boiled down to a question “Can SQL plan change without a change in plan_hash_value?”. What do you think? Please answer in the poll below.
plan_hash_value and hash_value
Hash_value of a SQL statement is generated from the text of an SQL statement and plan_hash_value is generated from the execution plan of that SQL statement[ More accurately, from that child cursors' execution plan and exactly what is involved in generating plan_hash_value is not published]. It is a general belief that plan_hash_value will change even if there is a slightest change in the execution plan. But, that is not always the case!
Test case
We will use a small table to explore this issue.
prompt Test case #1: Initial test case with index on columns (n1, n2). prompt ========== create table t1 (n1 number, n2 number, v1 varchar2(100)); create index t1_n1 on t1(n1,n2); explain plan for select * from t1 where n1=:b1 and n2=:b2; select * from table (dbms_xplan.display); prompt plan hash value in this case is 626762252 Plan hash value: 626762252 ------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 53 | 4134 | 4 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| T1 | 53 | 4134 | 4 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | T1_N1 | 1 | | 3 (0)| 00:00:01 | ------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("N1"=TO_NUMBER(:B1) AND "N2"=TO_NUMBER(:B2))
So, we got the plan_hash_value as highlighted above. In the following test case we will recreate the index reordering the columns as (n2, n1).
prompt Test case #2: Index with re-ordered columns prompt ========== drop index t1_n1; create index t1_n1 on t1(n2,n1); Plan hash value: 626762252 ------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 53 | 4134 | 9 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| T1 | 53 | 4134 | 9 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | T1_N1 | 5298 | | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("N2"=TO_NUMBER(:B2) AND "N1"=TO_NUMBER(:B1))
Notice that in the test result above filter predicate changed reflecting index column reordering. But the plan_hash_value did not change. Point is that execution plan can change without a change in plan_hash_value (due to change in the underlying tables).
Let’s modify that index dropping a column from the index in the next test case.
prompt Test case #3: dropping a column from index t1_n1. drop index t1_n1; create index t1_n1 on t1(n1); explain plan for select * from t1 where n1=:b1 and n2=:b2; Plan hash value: 626762252 ------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 53 | 4134 | 41 (3)| 00:00:01 | |* 1 | TABLE ACCESS BY INDEX ROWID| T1 | 53 | 4134 | 41 (3)| 00:00:01 | |* 2 | INDEX RANGE SCAN | T1_N1 | 2119 | | 3 (0)| 00:00:01 | ------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("N2"=TO_NUMBER(:B2)) 2 - access("N1"=TO_NUMBER(:B1))
We see that predicates changed but the plan_hash_value did not change. In the test case below, we will modify index to be a function based index and test this SQL statement. There are also few more self-evident test cases below.
prompt Test case #3: Index is a function based index. Still, no change in the plan_hash_value. create index t1_n1 on t1(to_char(n1)); explain plan for select * from t1 where to_char(n1)=:b1 and n2=:b2; Plan hash value: 626762252 ------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 53 | 4134 | 127 (1)| 00:00:01 | |* 1 | TABLE ACCESS BY INDEX ROWID| T1 | 53 | 4134 | 127 (1)| 00:00:01 | |* 2 | INDEX RANGE SCAN | T1_N1 | 2119 | | 3 (0)| 00:00:01 | ------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("N2"=TO_NUMBER(:B2)) 2 - access(TO_CHAR("N1")=:B1) prompt Test case #4: Different schema and same SQL. plan_hash_value did not change. Plan hash value: 626762252 ------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 78 | 2 (0)| 00:00:01 | |* 1 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 78 | 2 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | T1_N1 | 3 | | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("N2"=TO_NUMBER(:B2)) 2 - access("N1"=TO_NUMBER(:B1))
plan_hash_value is also case sensitive for index names. In the test cases below, we will create case sensitive indices.
prompt Test case #5: Lower case index_name.. plan_hash_value changed. create index "t1_n1" on t1(n1,n2); explain plan for select * from t1 where n1=:b1 and n2=:b2; Plan hash value: 2252949961 ------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 53 | 4134 | 4 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| T1 | 53 | 4134 | 4 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | t1_n1 | 1 | | 3 (0)| 00:00:01 | ------------------------------------------------------------------------------------- prompt Test case #6: Upper case index_name.. plan_hash_value did not change. create index "T1_N1" on t1(n1,n2); explain plan for select * from t1 where n1=:b1 and n2=:b2; Plan hash value: 626762252 ------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 53 | 4134 | 4 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| T1 | 53 | 4134 | 4 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | T1_N1 | 1 | | 3 (0)| 00:00:01 | ------------------------------------------------------------------------------------- prompt Test case #7: Space in the index name changes plan_hash_value though. create index "T1_N1 " on t1(n1,n2); explain plan for select * from t1 where n1=:b1 and n2=:b2; Plan hash value: 1377541522 -------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 53 | 4134 | 4 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| T1 | 53 | 4134 | 4 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | T1_N1 | 1 | | 3 (0)| 00:00:01 | --------------------------------------------------------------------------------------
Summary
In summary, plan_hash_value is a very good indicator to see if the plan changed or not. Don’t get me wrong, I also use plan_hash_value, but in addition to comparing CPU time and elapsed time. Execution plan can change even when there is no change to the plan_hash_value. Salient points from these test cases are:
Back to our problem. One of the index was recreated removing a column and caused optimizer to apply filter predicates at table level increasing number of accesses to table block tremendously, leading to more logical reads, more Global cache waits etc. This problem was amplified since this SQL was executed very frequently and concurrently from all RAC instances.
In my client’s defense, this application change was tested thoroughly. But, alas, test data chosen for this performance test was not probing this specific issue. This performance issue did not show up in development. Essentially, chosen data in the performance benchmark suite was not an excellent choice.
As they say in Gaelic language “Go raimh maith agat” to my client for allowing me to post this blog.
This can be read in traditional format in
plan_hash_value_and_gc
Update 1: Added the document and also corrected a typo.
One of the recurring challenges of performance tuning PeopleSoft systems is that having identified a problem SQL statement with the aid of a database utility, such as Oracle Enterprise Manager or session trace, you often need to identify the source so that you can implement a change.
Stored Outlines and SQL Profiles do not work well with PeopleSoft. Bind variables in Application Engines often become literals before the SQL statement is submitted to the database, unless the ReUse Statement feature has been enabled, which it is not by default.
One of the recurring challenges of performance tuning PeopleSoft systems is that having identified a problem SQL statement with the aid of a database utility, such as Oracle Enterprise Manager or session trace, you often need to identify the source so that you can implement a change.
Stored Outlines and SQL Profiles do not work well with PeopleSoft. Bind variables in Application Engines often become literals before the SQL statement is submitted to the database, unless the ReUse Statement feature has been enabled, which it is not by default.
When I made the first post to this blog, I had several ideas for future entries.
Those seem to have vanished with the wind, as I could not remember any of them.
Google notebook to the rescue - I have started recording blog ideas.
Now I just need more time to write entries here.
Now that I've made my lame excuse for ignoring this for a few weeks, I will move on to the technical content.
We've recently had a requirement to move some data from one application into another. The apps are similar in purpose, but not identical.
One of challenges in doing this is to cleanup the data into a form that our production app can use.
The source of the data has allowed a revision number for part numbers to be an alphanumeric column in a table. As you well might guess, this 'feature' was somewhat abused.
The data for a single part number may contain numerous rows in a table, each with a different revision number. Some of the revision 'numbers' are numeric, some are alpha.
We are only interested in those that are numeric.
For example, consider the following example:
Part# Revision
----- ----------
123 AA
123 AB
123 AC
123 01
123 02
123 03
The parts we need to import into our system are only those with revisions of 01,02 or 03.
The problem was how to exclude non-numeric revisions in a table.
You might consider the following SQL statement adequate
select * from parts where revision in ('01','02','03',...)
The IN operator could be converted to use a table known numeric revisions
select * from parts where revision in (select revision from good_revisions)
That would work, but would require building the table, which I would rather not do.
The data has not all arrived at one fell swoop - the next batch of data could break this method unless the table good_revisions is maintained. Ugh.
Compounding the problem is that the data is not consistent.
The revision could be ' 2' or '2 ' or '02'.
It seemed that this would call for regular expression.
Had I built the migration schemas in 10g database I could have used the REGEXP_INSTR function to find the numeric only revisions.
As the application is still in 9i, I used a 9i database to build the migration schemas.
Exercising my flawless 20/20 hindsight I realized I should have used 10g for those schemas.
Too late to change now, not enough time to move everything.
The next choice is to use the OWA_PATTERN package to find numeric only revisions.
From a purely functional perspective, this works perfectly.
From a 'waiting around staring at the computer while a job finishes' it was less than perfect.
Using OWA_PATTERN practically guarantees that any SQL it is used in will be slow.
There had to be a faster way.
At that point the TRANSLATE function came to mind.
While I have always found this function rather obtuse, it seemed it might do the job.
The TRANSLATE function accepts a string, a list of characters to locate in the string, and a list of characters to translate them to.
How can that be used to detect numeric only character data?
It works by transforming all numeric characters into a known character, and checking to see if the string consists solely of that character.
An example is the best way to show this.
Create a test table:
create table detect_numeric
as
select cast(chr(a.rownumber) || chr(b.rownumber) as varchar2(2)) fake_number
from
( select rownum rownumber from all_Tables where rownum <= 1000 ) a,( select rownum rownumber from all_Tables where rownum <= 1000 ) bwhere a.rownumber between 48 and 90and b.rownumber between 48 and 90/
Here's an example of using OWA_PATTERN to find numeric data:
SQL> l
1 select fake_number
2 from detect_numeric
3 where owa_pattern.amatch(fake_number,1,'^\d+$') > 0
4* order by 1
SQL> /
FA
--
00
01
...
98
99
100 rows selected.
Here's how to do the same thing using the TRANSLATE function
SQL> l
1 select fake_number
2 from detect_numeric
3 where '||' = translate(fake_number,'0123456789','||||||||||')
4* order by 1
SQL> /
FA
--
00
01
...
98
99
100 rows selected.
The TRANSLATE function works by changing all numeric characters to the '|' symbol.
If the returned value is '||' then this must be all numeric data.
There's a caveat with using this method. The character used in the TRANSLATE function must not appear in the data being checked.
This example is simplified in that it does not account for nulls, spaces or varying data lengths.
Nonetheless it works quite well.
Is it faster?
In the test I ran the TRANSLATE function is 2 orders or magnitude faster than when using OWA_PATTERN.
Tom Kyte's run_stats was used to compare the run times and resource usage of both methods.
Run Stats
Running both methods in loop 20 times yielded the following run times:
10.265529 secs
.010235 secs
PL/SQL procedure successfully completed.
OWA_PATTERN required 10 seconds, while the TRANSLATE function did the same thing in 0.01 seconds.
The resource usage was much better for TRANSLATE as well;
SQL> @run_stats
NAME RUN1 RUN2 DIFF
---------------------------------------- ---------- ---------- ----------
...
LATCH.SQL memory manager workarea list l 268 0 -268
atch
LATCH.checkpoint queue latch 640 0 -640
STAT...redo size 27764 28404 640
STAT...Elapsed Time 1028 3 -1025
STAT...recursive cpu usage 1029 3 -1026
STAT...session pga memory max 74048 65536 -8512
STAT...session uga memory 65464 0 -65464
STAT...session uga memory max 65464 0 -65464
STAT...session pga memory 74048 0 -74048
49 rows selected.
As long as was playing with this it seemed a good idea to test on 10g as well, using the REGEXP_INSTR function rather than the slow OWA_PATTERN package.
The results were surprising.
While REGEXPR_INSTR was very fast, TRANSLATE was still the faster method.
.048662 secs
.008829 secs
PL/SQL procedure successfully completed.
The TRANSLATE function appears to be another order of magnitude faster on 10gR2 as on 9i.
That should not be too big a surprise as there were many PL/SQL optimization included in 10gR2, but this was still somewhat unexpected.
------
Niall Litchfield Niall Litchfield's Blog wondered why I had not tried using an is_number function such as the one shown below.
create or replace function is_number( chk_data_in varchar2 )
return number
is
dummy number(38,4);
begin
dummy := to_number(chk_data_in);
return 1;
exception
when value_error then
return 0;
when others then
raise;
end;
Quite frankly, I forgot about it.
Here are timings for both 9i and 10g. As expected, TRANSLATE is still quite a bit faster.
9i:
SQL> @th3
.092713 secs
.009951 secs
PL/SQL procedure successfully completed.
10gR2:
SQL> @th3
.362097 secs
.008479 secs
PL/SQL procedure successfully completed.
DBAs from time to time must write shell scripts. If your environment is strictly Windows based, this article may hold little interest for you.
Many DBAs however rely on shell scripting to manage databases. Even if you use OEM for many tasks, you likely use shell scripts to manage some aspects of DBA work.
Lately I have been writing a number of scripts to manage database statistics - gathering, deleting, and importing exporting both to and from statistics tables exp files.
Years ago I started using the shell builtin getopts to gather arguments from the command line. A typical use might look like the following:
while getopts d:u:s:T:t:n: arg
do
case $arg inSo Oracle 11gR2 was released earlier this week and I am an trying to install it on a 4-node cluster. I had previously installed the beta release on the same hardware/operating system configuration, so I was reasonably confident that a similar Advanced installation would work first time.
All went well until the final CVU check. This is the final step in the CVU installation. Oracle 11.2 requires SCAN (Single Client Access Names) to be specified. Three SCAN addresses are required for each cluster, irrespective of the number of nodes. The SCAN addresses are maintained as VIPs by the Clusterware. Instead of connecting to listener VIPs, in Oracle 11.2 applications should connect to the SCAN VIPs and they will then get redirected to the listeners.
In the beta it was possible to configure the SCAN addresses in /etc/hosts. For example I have been using:
192.168.2.97 cluster3-scan.juliandyke.com cluster3
192.168.2.98 cluster3-scan.juliandyke.com cluster3
192.168.2.99 cluster3-scan.juliandyke.com cluster3
Probably incorrect from a networking perspective, but enough to get through the installation procedure.
In the production version, CVU checks that the SCAN address is set in DNS. Tracing the CVU shows that it looks in /etc/nsswitch.conf and then runs nslookup. By default /etc/nsswitch.conf includes
hosts: files dns
which means look in /etc/hosts first and if you don't find the name there then go to DNS. CVU does check /etc/nsswitch.conf, but only to grumble if the hosts entry does not include dns. If the dns entry is present CVU will run nslookup. If it is not present, it does not, however, use /etc/hosts.
Two of the top features in 11gR2 are the ASM Dynamic Volume Manager (ADVM) and ASM Clustered File System (ACFS). What is the big deal about these two?
ADVM allows you to create a volume from an ASM diskgroup. Here is an example where we created a volume called asm_vol1 of 100 MB on a diskgroup called DATA:
ASMCMD [+] > volcreate -G DATA -s 100M asm_vol1
Internally it issues the command
alter diskgroup DATA add volume 'asm_vol1' size 100M;
Now you enable the volume you just created:
ASMCMD [+] > volenable -G DATA asm_vol1
Internally it issues:
alter diskgroup DATA enable volume 'asm_vol1';
You can perform other commands like resize, delete, disable; but more on that later on a full length article.
Now that the volume is created, what can you do with it. Well, like all volumes, you can create a filesystem on it. Here is an example of creating a FS called acfs1:
[root@oradba2 ~]# mkdir /acfs1
[root@oradba2 ~]# /sbin/mkfs -t acfs /dev/asm/asm_vol1-207
mkfs.acfs: version = 11.2.0.1.0.0
mkfs.acfs: on-disk version = 39.0
mkfs.acfs: volume = /dev/asm/asm_vol1-207
mkfs.acfs: volume size = 268435456
Register MountPoint Command:
[root@oradba2 ~]# /sbin/acfsutil registry -a -f /dev/asm/asm_vol1-207 /acfs1
acfsutil registry: mount point /acfs1 successfully added to Oracle Registry
If you get an error, use the force option:
[root@oradba2 /]# /sbin/mkfs.acfs -f /dev/asm/asm_vol1-207
mkfs.acfs: version = 11.2.0.1.0.0
mkfs.acfs: on-disk version = 39.0
mkfs.acfs: volume = /dev/asm/asm_vol1-207
Recent comments
21 weeks 2 days ago
31 weeks 11 hours ago
32 weeks 5 days ago
35 weeks 6 days ago
38 weeks 1 day ago
47 weeks 5 days ago
49 weeks 2 days ago
50 weeks 2 days ago
50 weeks 3 days ago
1 year 1 week ago