Reputation: 37382
I had a very strange performance related problem with MERGE
in Oracle 10. In a few words, I have a stored procedure that calculates and stores user rank based on her activity in the system and contains just one MERGE
statement:
MERGE INTO user_ranks target USING
([complex query that returns rank_id and user_id])src ON
(src.user_id = target.user_id)
WHEN MATCHED THEN UPDATE SET target.rank_id = src.rank_id
WHEN NOT MATCHED THEN INSERT (target.user_id, target.rank_id)
VALUES (src.user_id, src.rank_id);
// user_ranks table structure:
CREATE TABLE user_ranks (user_id INT NOT NULL
PRIMARY KEY USING INDEX (CREATE UNIQUE INDEX UQ_uid_uranks ON user_ranks(user_id)),
rank_id INT NOT NULL,
CONSTRAINT FK_uid_uranks FOREIGN KEY (user_id) REFERENCES users(id),
CONSTRAINT FK_rid_uranks FOREIGN KEY(rank_id) REFERENCES ranks(id));
// no index on rank_id - intentionally, ranks table is a lookup with
// a very few records and no delete/update allowed
The subquery which is used as a source for MERGE
returns at most 1 record (user_id is passed as parameter to the procedure). It's quite expensive but execution time is acceptable (1-1.2 sec). The problem is that MERGE
execution time hikes to more than 40 seconds, and I have no clue why. I tried using LEADING
hint with no success. But if I split the statement into 2 parts, first one - run SELECT
subquery and store result(rank_id) into variable and then merge - MERGE ... USING (SELECT user_id, rank_id FROM DUAL)src ...
everything works just fine. From what I read, there are known issues with Oracle's MERGE
, but they are mostly related to triggers (no triggers in my case). It also says that MERGE
works slower than combination of INSERT
and UPDATE
, but I believe the "normal" difference is around 5-10%, not 30 times...
I'm trying to understand what I did wrong... Thanks for your suggestions.
Update
Execution plan is quite long to post it here, in short : subquery cost by itself is 12737, with merge
- 76305. Stats output for merge
:
108 recursive calls
4 db block gets
45630447 consistent gets
24905 physical reads
0 redo size
620 bytes sent via SQL*Net to client
1183 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
subquery alone :
8 recursive calls
0 db block gets
34 consistent gets
0 physical reads
0 redo size
558 bytes sent via SQL*Net to client
234 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
Upvotes: 4
Views: 3245
Reputation: 3985
If you've set up SQL*Plus autotrace it would be a matter of seconds to see which part of the actual execution plan has caused the most physical and logical I/O and used the most memory.
Note that the information you get using this method is much more precise than a simple explain plan
.
Upvotes: 2