This has been bugging me for about 4 hours, so I thought it’s time to seek some help. I can’t find anything similar online, but mainly because the values are quite specific and I’m not really sure what to look for…
This is a problem I’m having with an Oracle script, running in SQLPlus 10.2.0.5.
The Problem:
(Names and real data has been changed to protect the identity of the suspects)
I have a table called MONKEYS and a table called MONKEY_PUZZLES, which look a bit like this:
MONKEYS
- MONKEY_ID
- GIRAFFE_ID
- MONKEY_PUZZLE_ID
MONKEY_PUZZLES
- MONKEY_PUZZLE_ID
- GIRAFFE_ID
MONKEY_PUZZLES.GIRAFFE_ID and MONKEYS.GIRAFFE_ID match, but there are n MONKEYS per MONKEY_PUZZLE (so MONKEY_PUZZLES.GIRAFFE_ID 1 might match to MONKEY.MONKEY_ID 1, 2 and 334).
I want to set the MONKEYS.MONKEY_PUZZLE_ID field, based on the MONKEY_PUZZLES.MONKEY_PUZZLE_ID field, because currently my MONKEYS.MONKEY_PUZZLE_ID field is null. I have indexes on:
- MONKEYS.MONKEY_ID (primary key with index)
- MONKEYS.GIRAFFE_ID
- MONKEY_PUZZLES.MONKEY_PUZZLE_ID (primary key with index)
- MONKEY_PUZZLES.GIRAFFE_ID
I also have over 1.6 million rows in the MONKEYS table and over 50,000 rows in the MONKEY_PUZZLES table.
I was originally using the following query:
UPDATE MONKEYS M SET M.MONKEY_PUZZLE_ID =
(SELECT MP.MONKEY_PUZZLE_ID FROM MONKEY_PUZZLES MP
WHERE M.GIRAFFE_ID = MP.GIRAFFE_ID
AND MP.GIRAFFE_ID IS NOT NULL);
However, this script would take about 2 minutes to get from 0% to 92.67% complete, then it took well over 25 minutes to get to 94% complete. I eventually stopped the script. I ran it a few times (I was playing about with different indexes and DBMS_STATS), but each time it would get to 92.67% and crap out.
So I thought it must be my script. I went back to square one, ate a banana, said “Oook” a lot and came up with the following variation script, which is much more explicit:
UPDATE MONKEYS M2 SET M2.MONKEY_PUZZLE_ID =
(SELECT X.MPID FROM
(SELECT M.MONKEY_ID MID, MP.MONKEY_PUZZLE_ID MPID
FROM MONKEYS M INNER JOIN MONKEY_PUZZLES MP
ON MP.GIRAFFE_ID = M.GIRAFFE_ID) X
WHERE X.MID = M2.MONKEY_ID);
However, it was rubbish. Even with my indexes, it took well over 5 minutes just to get to 2%, so I scrapped that one.
I then came up with the following variation, which I was pretty pleased with:
UPDATE MONKEYS M SET M.MONKEY_PUZZLE_ID =
(SELECT MP.MONKEY_PUZZLE_ID
FROM MONKEY_PUZZLES MP
WHERE M.GIRAFFE_ID = MP.GIRAFFE_ID
AND EXISTS
(SELECT 1 FROM MONKEY_PUZZLES MP2
WHERE M.GIRAFFE_ID = MP2.GIRAFFE_ID));
However, and I really couldn’t believe my eyes, this script behaved almost exactly like the first; running in about 2 minutes, from 0% up to 92.67% complete, then taking absolutely ages to get any further. What is it with 92.67%?!
The total number of blocks is 41,717, so it gets to about 38,000 blocks before it seems to significantly slow down.
In case you’re wondering, I’m using the following query on a separate SQLPlus session to calculate the %age complete:
SELECT X.*, TO_CHAR(SYSDATE, 'HH24:MI:SS') TIMESTAMP
FROM (select sid, serial#, opname, sofar, totalwork,
round(sofar/totalwork*100,2) "% Complete" from v$session_longops) X
WHERE "% Complete" < 100 and totalwork > 0;
(which is a variation of this: http://searchoracle.techtarget.com/tip/Tracking-the-progress-of-long-running-queries)
Please help put a poor tiger out of his misery!
P.S. I’m going to leave the script running overnight and I’ll update in the morning if it reaches 100%.
EDIT: It reached 100% eventually after only 57minutes (so not all that bad), but considering it reached 92.67% in 2 minutes, it’s pretty terrible!
I have no explanation for the specific symptoms you’re seeing — possibly the execution plan for the update would help explain it — but in any case I would expect this to perform better: