Los latches, mutexes y buffers protegen estructuras de la memoria de la base de datos: mientras una sesión trabaja en una de ellas, las otras que la necesitan esperan su turno. La causa suele ser parse en exceso, un bloque caliente o un objeto modificado mientras está en uso.
Síntomas
En el Diagnóstico aparece el hallazgo Contención por estructuras internas de la base de datos (CONCORRENCIA). El texto dice cuánto del tiempo total quedó en la clase de espera Concurrency, en porcentaje y en segundos, lista los eventos de la clase que pesaron y termina con Ver perfil por clase.
El hallazgo aparece cuando la clase Concurrency pesa en el tiempo total. La gravedad es media y pasa a alta cuando la clase pesa más. Entran en esta clase estos eventos, por la wait_class de Oracle 19c:
| Contención | Eventos |
|---|---|
| Bloques | buffer busy waits, latch: cache buffers chains, enq: TX - index contention |
| Parse y cursores | latch: shared pool, latch: library cache, library cache lock, library cache pin, library cache load lock, library cache: mutex X, cursor: pin S wait on X, cursor: pin S, cursor: pin X, cursor: mutex S, cursor: mutex X |
| Diccionario | latch: row cache objects, row cache lock, row cache mutex |
| Otros | latch: In memory undo latch, os thread startup |
read by other session queda en User I/O y latch free en Other: no cuentan aquí. Un evento que el informe no clasifica aparece en el perfil como No clasificado.
Causa
La sesión del trace disputó una estructura con otras sesiones que se ejecutaban al mismo tiempo. El trace muestra solo la espera de esta sesión; quien la disputaba con ella queda fuera de él. Causas comunes, de la más a la menos frecuente:
- Parse en exceso: SQL con literales en lugar de bind variables, o un cursor abierto y cerrado en cada ejecución. Pesa en los latches de la shared pool y de la library cache y en los mutexes de cursor.
- Bloque caliente por lectura lógica: muchas sesiones leyendo los mismos bloques, en general por un plan que vuelve a leer los mismos bloques (
latch: cache buffers chains). - Inserción concurrente en el mismo bloque: un índice con clave creciente (secuencia, fecha) que recibe inserciones de varias sesiones (
buffer busy waits,enq: TX - index contention). - Secuencia sin caché o con caché pequeña (
row cache lock). - DDL, compilación o recolección de estadísticas en un objeto en uso, y cursores invalidados con frecuencia (
library cache lock,library cache pin,cursor: pin S wait on X). - Consulta en paralelo que inicia los procesos de ejecución paralela (
os thread startup).
Solución
Empiece por el evento que el hallazgo lista primero. Los casos van del más al menos común.
Parse en exceso
Revise la fila PARSE en Tipos de llamada: Misses cerca de Cantidad significa hard parse en casi todo PARSE. Si el Diagnóstico también muestra uno de estos hallazgos, empiece por él:
Bloque caliente por lectura lógica
Con latch: cache buffers chains, reduzca las lecturas lógicas de las sentencias que esperaron. Busque esas sentencias en el grupo Concurrency de Sentencias principales por componente y por clase y, en el plan de cada una, la operación con LIO propio alto (operación que concentra el plan). El caso típico es un NESTED LOOPS que vuelve a leer los mismos bloques miles de veces.
Inserción concurrente en el mismo bloque
En Objetos con más tiempo de espera, busque el índice o la tabla de las esperas. Para un índice con clave creciente, las salidas habituales son el índice reverse key y el índice global particionado por hash:
alter index exemplo.pedido_pk rebuild reverse;
create index exemplo.pedido_data_ix on exemplo.pedido (data_pedido)
global partition by hash (data_pedido) partitions 16;
El índice reverse key deja de servir para búsquedas por rango en la clave (between, >), y el índice particionado exige la opción Partitioning. Para una tabla con buffer busy waits en inserción, compruebe si el segmento está en un tablespace con gestión automática del espacio (ASSM).
Secuencia sin caché
Para row cache lock en sentencias que usan NEXTVAL, revise la caché de las secuencias del esquema y aumente la de las más usadas:
select sequence_owner, sequence_name, cache_size, order_flag
from dba_sequences
where sequence_owner = 'EXEMPLO'
order by cache_size;
alter sequence exemplo.pedido_seq cache 1000;
Con o sin caché, una secuencia no garantiza números sin huecos: el rollback también salta números. La caché solo aumenta el hueco cuando la instancia cae o la secuencia sale de la shared pool.
Objeto modificado mientras está en uso
El DDL, la compilación de paquetes y la recolección de estadísticas invalidan los cursores que usan el objeto, y las sesiones se detienen para rehacer el parse. Lleve esas tareas fuera del horario de uso. Si la sentencia tiene demasiadas versiones del cursor, v$sql_shared_cursor indica el motivo de cada versión:
select sql_id, version_count, executions, parse_calls
from v$sqlarea
order by version_count desc
fetch first 20 rows only;
Procesos de consulta en paralelo
os thread startup es el tiempo de iniciar procesos, en general los de ejecución paralela. Revise el grado de paralelismo de las sentencias que esperaron: en una sentencia corta y muy ejecutada, el paralelismo cuesta más de lo que gana.
Cómo confirmar
La contención depende de cuántas sesiones hacen lo mismo al mismo tiempo. Un trace tomado con la base de datos vacía no prueba la corrección: mida en el mismo horario y con la misma carga de la medición anterior.
- Aplique una corrección por vez.
- Active el trace de la misma operación con esperas (activar el trace con DBMS_MONITOR).
- Envíe el archivo nuevo y abra el informe.
- Compare la fila Concurrency en Por clase de espera y las filas de los eventos en el perfil.
Resultado: la clase Concurrency deja de pesar en el tiempo total y el hallazgo desaparece del Diagnóstico. Cuando la causa era el parse, la columna Misses de la fila PARSE en Tipos de llamada también baja.
En el informe
El informe de ejemplo no tiene esperas de la clase Concurrency; los enlaces abren las mismas secciones en él.
- Diagnóstico: el hallazgo, con el enlace Ver perfil por clase.
- Por clase de espera: la fila Concurrency, con los eventos en Componentes principales (clases de espera).
- Perfil del tiempo de respuesta: cada evento, con Ocurrencias y Media (s). Haga clic en el nombre del evento para abrir la tarjeta con la causa típica y dónde mirar (perfil).
- Sentencias principales por componente y por clase: las sentencias que disputaron (sentencias por componente).
- Tipos de llamada: Cantidad y Misses de la fila PARSE (tipos de llamada).
- Objetos con más tiempo de espera: el índice o la tabla disputada (objetos).
- Textos compartibles: sentencias que solo difieren en literales. La sección solo aparece cuando el trace tiene esas sentencias (textos compartibles).