Boa tarde amigos DBAs.
Hoje venho falar sobre um dos maiores terrores para Performance de um Banco Oracle:
Uma query com WHERE rownum = :valor;
Bom, isso pode parecer algo normal à primeira vista. Claro, uma query onde você quer que seja exibida somente a enésima linha do resultado. Nada muito trabalhoso.
Okay. Agora eu lhes pergunto. Imagine Uma tabela com Milhões de linhas, que é consultada (Join) com uma outra tabela com milhões de linhas, o resultado desse join deve ser ordenado e SOMENTE a primeira linha deve ser capturada e utilizada como subquery para uma query Pai, que é a responsável por trazer os registros que o cliente precisa.
:)
Complicado de entender? Okay.. aqui vai um caso real.
Obs: O nome de todas as tabelas e colunas foram alterados para preservar o cliente.
Em um determinado servidor, identifiquei que de tempos em tempos (Aproximadamente de 15 em 15 minutos, a CPU era utilizada em 98% por um único processo do SO, pertencente ao oracle).
*Linhas do utilitário TOP exibindo os processos que mais consumiam a cpu do servidor:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
27261 oracle 20 0 5369m 391m 385m R 99.9 0.6 2:46.64 oracle
27279 oracle 18 0 5369m 456m 449m R 98.9 0.7 3:43.36 oracle
Com base nos PIDs encontrados, podemos pesquisar ao que se referem.
Obs: O PID informado pelo SO é correspondente ao SPID da view v$process do oracle.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | column THREADID for a8 column USERNAME for a15 select vp.spid THREADID, s.USERNAME, s.STATUS, t.PIECE, t.SQL_TEXT from v$session s, v$sqltext t , v$process vp where s.SQL_ADDRESS = t.ADDRESS and vp.addr = s.paddr(+) and vp.spid in (27261, 27279) order by s.sid, t.PIECE; |
Aqui o resultado obtido:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | THREADID USERNAME STATUS PIECE SQL_TEXT -------- -------- -------- ----- ---------------------------------------------------------------- 27279 USRWEB ACTIVE 0 SELECT main.ID_CANCEL_REASON as IDT1_0_, main.TX_CANCEL_REASON 27279 USRWEB ACTIVE 1 as TX2_43_0_ FROM cancel_request_reason main, ( 27279 USRWEB ACTIVE 2 SELECT cpd.id_cancel_req_reason, cpd.id_cancel_req 27279 USRWEB ACTIVE 3 FROM cancel_request_detail cpd, cancel_request 27279 USRWEB ACTIVE 4 cp, cancel_request_detail_dept cdct WHERE (cpd.id_cancel_req 27279 USRWEB ACTIVE 5 = cp.id_cancel_req OR cdct.id_cancel_req 27279 USRWEB ACTIVE 6 = cp.id_cancel_req) AND cp.id_online_request 27279 USRWEB ACTIVE 7 = :"SYS_B_0" AND rownum = :"SYS_B_1") ma 27279 USRWEB ACTIVE 8 in_1_ WHERE main.ID_CANCEL_REASON = main_1_. 27279 USRWEB ACTIVE 9 id_cancel_req_reason 27261 USRWEB ACTIVE 0 SELECT main.ID_CANCEL_REASON as IDT1_0_, main.TX_CANCEL_REASON 27261 USRWEB ACTIVE 1 as TX2_43_0_ FROM cancel_request_reason main, ( 27261 USRWEB ACTIVE 2 SELECT cpd.id_cancel_req_reason, cpd.id_cancel_req 27261 USRWEB ACTIVE 3 FROM cancel_request_detail cpd, cancel_request 27261 USRWEB ACTIVE 4 cp, cancel_request_detail_dept cdct WHERE (cpd.id_cancel_req 27261 USRWEB ACTIVE 5 = cp.id_cancel_req OR cdct.id_cancel_req 27261 USRWEB ACTIVE 6 = cp.id_cancel_req) AND cp.id_online_request 27261 USRWEB ACTIVE 7 = :"SYS_B_0" AND rownum = :"SYS_B_1") ma 27261 USRWEB ACTIVE 8 in_1_ WHERE main.ID_CANCEL_REASON = main_1_. 27261 USRWEB ACTIVE 9 id_cancel_req_reason |
Formatei a query para que ficasse legível:
1 2 3 4 5 6 7 8 9 10 11 12 | SELECT main.ID_CANCEL_REASON as IDT1_0_, main.TX_CANCEL_REASON as TX2_43_0_ FROM USRWEB.cancel_request_reason main, (SELECT cpd.id_cancel_req_reason, cpd.id_cancel_req FROM USRWEB.cancel_request_detail cpd, USRWEB.cancel_request cp, USRWEB.cancel_request_detail_dept cdct WHERE (cpd.id_cancel_req = cp.id_cancel_req OR cdct.id_cancel_req = cp.id_cancel_req) AND cp.id_online_request = :"SYS_B_0" AND rownum = :"SYS_B_1") main_1_ WHERE main.ID_CANCEL_REASON = main_1_.id_cancel_req_reason; |
Como você pode observar, durante a execução desta query, o consumo de CPU chega em 99% apenas para o processo em execução.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | SQL> SELECT main.ID_CANCEL_REASON as IDT1_0_, main.TX_CANCEL_REASON as TX2_43_0_ FROM USRWEB.cancel_request_reason main, (SELECT cpd.id_cancel_req_reason, cpd.id_cancel_req FROM USRWEB.cancel_request_detail cpd, USRWEB.cancel_request cp, USRWEB.cancel_request_detail_dept cdct WHERE (cpd.id_cancel_req = cp.id_cancel_req OR cdct.id_cancel_req = cp.id_cancel_req) AND cp.id_online_request = :"SYS_B_0" AND rownum = :"SYS_B_1") main_1_ WHERE main.ID_CANCEL_REASON = main_1_.id_cancel_req_reason; IDT1_0_ TX2_43_0_ ---------- ---------------------------------------------------------------------------------------------------- 14 Incorrect Credit Elapsed: 00:06:25.01 |
(Query re-escrita para termos ganho de performance)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | SQL> SELECT main.ID_CANCEL_REASON as IDT1_0_, main.TX_CANCEL_REASON as TX2_43_0_ FROM USRWEB.cancel_request_reason main, (select * from (SELECT cpd.id_cancel_req_reason, cpd.id_cancel_req, ROW_NUMBER() OVER (ORDER BY id_online_request) as rn FROM USRWEB.cancel_request_detail cpd, USRWEB.cancel_request cp, USRWEB.cancel_request_detail_dept cdct WHERE (cpd.id_cancel_req = cp.id_cancel_req OR cdct.id_cancel_req = cp.id_cancel_req) AND cp.id_online_request = :"SYS_B_0") where rn = :"SYS_B_1") main_1_ WHERE main.ID_CANCEL_REASON = main_1_.id_cancel_req_reason; IDT1_0_ TX2_43_0_ ---------- ---------------------------------------------------------------------------------------------------- 14 Incorrect Credit Elapsed: 00:00:06.04 |
Como pode notar, após a modificação da query, a mesma caiu de 6.25 Minutos para 6.04 Segundos.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | PLAN_TABLE_OUTPUT ------------------------------------------------------------------------------------------------------------------ Plan hash value: 3866720052 ------------------------------------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 36M| 1691M| 7266 (9)| 00:01:28 | |* 1 | HASH JOIN | | 36M| 1691M| 7266 (9)| 00:01:28 | | 2 | TABLE ACCESS FULL | cancel_request_reason | 8 | 184 | 3 (0)| 00:00:01 | |* 3 | VIEW | | 36M| 897M| 7021 (5)| 00:01:25 | |* 4 | WINDOW NOSORT STOPKEY | | 36M| 828M| 7021 (5)| 00:01:25 | | 5 | CONCATENATION | | | | | | |* 6 | HASH JOIN | | 36M| 828M| 7000 (5)| 00:01:25 | | 7 | INDEX FAST FULL SCAN | IDX_CARD_DEPT_CAN_DET_001 | 954 | 4770 | 2 (0)| 00:00:01 | | 8 | MERGE JOIN CARTESIAN | | 5727K| 103M| 6756 (2)| 00:01:22 | | 9 | TABLE ACCESS BY INDEX ROWID| cancel_request | 1 | 11 | 5 (0)| 00:00:01 | |* 10 | INDEX RANGE SCAN | CANC_REQUEST_ORA_001 | 1 | | 3 (0)| 00:00:01 | | 11 | BUFFER SORT | | 4789K| 36M| 6751 (2)| 00:01:22 | | 12 | TABLE ACCESS FULL | cancel_request_detail | 4789K| 36M| 6751 (2)| 00:01:22 | | 13 | NESTED LOOPS | | 868 | 20832 | 20 (0)| 00:00:01 | | 14 | NESTED LOOPS | | 18 | 342 | 8 (0)| 00:00:01 | | 15 | TABLE ACCESS BY INDEX ROWID| cancel_request | 1 | 11 | 5 (0)| 00:00:01 | |* 16 | INDEX RANGE SCAN | CANC_REQUEST_ORA_001 | 1 | | 3 (0)| 00:00:01 | | 17 | TABLE ACCESS BY INDEX ROWID| cancel_request_detail | 15 | 120 | 3 (0)| 00:00:01 | |* 18 | INDEX RANGE SCAN | IDX_CANC_REQ_DET_003 | 16 | | 2 (0)| 00:00:01 | |* 19 | INDEX FAST FULL SCAN | IDX_CARD_DEPT_CAN_DET_001 | 48 | 240 | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("MAIN"."ID_CANCEL_REASON"="from$_subquery$_003"."id_cancel_req_reason") 3 - filter("RN"=TO_NUMBER(:SYS_B_1)) 4 - filter(ROW_NUMBER() OVER ( ORDER BY "id_online_request")<=TO_NUMBER(:SYS_B_1)) 6 - access("CDCT"."id_cancel_req"="CP"."id_cancel_req") 10 - access("CP"."id_online_request"=TO_NUMBER(:SYS_B_0)) 16 - access("CP"."id_online_request"=TO_NUMBER(:SYS_B_0)) 18 - access("CPD"."id_cancel_req"="CP"."id_cancel_req") 19 - filter(LNNVL("CDCT"."id_cancel_req"="CP"."id_cancel_req")) 38 rows selected. |
Este é o plano de execução para a query Original:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | PLAN_TABLE_OUTPUT ------------------------------------------------------------------------------------------------------------------- Plan hash value: 3966394659 ------------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 36M| 897M| 7265 (9)| 00:01:28 | |* 1 | HASH JOIN | | 36M| 897M| 7265 (9)| 00:01:28 | | 2 | TABLE ACCESS FULL | cancel_request_reason | 8 | 184 | 3 (0)| 00:00:01 | | 3 | VIEW | | 36M| 103M| 7020 (5)| 00:01:25 | | 4 | COUNT | | | | | | | 5 | CONCATENATION | | | | | | |* 6 | FILTER | | | | | | |* 7 | HASH JOIN | | 36M| 828M| 7000 (5)| 00:01:25 | | 8 | INDEX FAST FULL SCAN | IDX_CARD_DEPT_CAN_DET_001 | 954 | 4770 | 2 (0)| 00:00:01 | | 9 | MERGE JOIN CARTESIAN | | 5727K| 103M| 6756 (2)| 00:01:22 | | 10 | TABLE ACCESS BY INDEX ROWID| cancel_request | 1 | 11 | 5 (0)| 00:00:01 | |* 11 | INDEX RANGE SCAN | CANC_REQUEST_ORA_001 | 1 | | 3 (0)| 00:00:01 | | 12 | BUFFER SORT | | 4789K| 36M| 6751 (2)| 00:01:22 | | 13 | TABLE ACCESS FULL | cancel_request_detail | 4789K| 36M| 6751 (2)| 00:01:22 | |* 14 | FILTER | | | | | | | 15 | NESTED LOOPS | | 868 | 20832 | 20 (0)| 00:00:01 | | 16 | NESTED LOOPS | | 18 | 342 | 8 (0)| 00:00:01 | | 17 | TABLE ACCESS BY INDEX ROWID| cancel_request | 1 | 11 | 5 (0)| 00:00:01 | |* 18 | INDEX RANGE SCAN | CANC_REQUEST_ORA_001 | 1 | | 3 (0)| 00:00:01 | | 19 | TABLE ACCESS BY INDEX ROWID| cancel_request_detail | 15 | 120 | 3 (0)| 00:00:01 | |* 20 | INDEX RANGE SCAN | IDX_CANC_REQ_DET_003 | 16 | | 2 (0)| 00:00:01 | |* 21 | INDEX FAST FULL SCAN | IDX_CARD_DEPT_CAN_DET_001 | 48 | 240 | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("MAIN"."ID_CANCEL_REASON"="MAIN_1_"."id_cancel_req_reason") 6 - filter(ROWNUM=TO_NUMBER(:SYS_B_1)) 7 - access("CDCT"."id_cancel_req"="CP"."id_cancel_req") 11 - access("CP"."id_online_request"=TO_NUMBER(:SYS_B_0)) 14 - filter(ROWNUM=TO_NUMBER(:SYS_B_1)) 18 - access("CP"."id_online_request"=TO_NUMBER(:SYS_B_0)) 20 - access("CPD"."id_cancel_req"="CP"."id_cancel_req") 21 - filter(LNNVL("CDCT"."id_cancel_req"="CP"."id_cancel_req")) 40 rows selected. |
Espero que tenham gostado.
Até o próximo post.
SOBRE O AUTOR
Com uma década de experiẽncia em produtos Oracle como DBA e também Developer, pretendo, com esse blog, trocar experiências com você, leitor.
Ótimo post... Uma coisa que podemos notar é que em uma query top-N o plano de execução fica "camuflado"... Esperimente na sua primeira query pegar o plano de execução da consulta como um todo e ver como ele é bem menor do que a consulta interna.
ResponderExcluirAbs,
Mufalani
Olá Rodrigo. Muito bem colocado.
ExcluirObrigado pelo comentário... Continue visitando =)