Performance de Db2

Visual Explain, SQL Query Engine (SQE), estrategias de indexacion, query governor, estadisticas del catalog, plan cache y proceso de tuning.

SQL Query Engine (SQE)

IBM i tiene dos motores de ejecucion de queries: el Classic Query Engine (CQE) legacy y el SQL Query Engine (SQE) moderno. SQE es el optimizador preferido desde IBM i 7.1 y maneja la mayoria de las consultas automaticamente.

CQE -- Classic Query Engine

  • Motor legacy, presente desde los origenes de AS/400
  • Basado en reglas y heuristicas simples
  • Usado para OPNQRYF y queries nativas antiguas
  • No soporta hash joins ni star schema joins
  • Limitado en paralelismo y optimizaciones avanzadas

SQE -- SQL Query Engine

  • Motor moderno, cost-based optimizer
  • Maneja hash joins, star joins, MQTs
  • Soporte completo de paralelismo (SMP)
  • Index Advisor integrado
  • Adaptive Query Processing (AQP) en 7.4+

Cost-Based Optimizer

SQE evalua multiples planes de ejecucion y elige el de menor costo estimado basandose en estadisticas del catalog.

Adaptive Query Processing

En IBM i 7.4+, SQE puede ajustar el plan de ejecucion en tiempo real si las estimaciones resultan incorrectas.

Star Schema Join

Optimizacion especifica para esquemas estrella en data warehousing. Combina bitmaps de multiples indices EVI.

SQL -- Verificar que motor se usa para una query
-- Ver queries recientes y su motor de optimizacion
SELECT
  SUBSTR(STATEMENT_TEXT, 1, 120) AS SQL_TEXT,
  QUERY_OPTIMIZATION_ENGINE AS MOTOR,
  AVERAGE_TOTAL_TIME AS TIEMPO_PROM_MS,
  TOTAL_TIMES_RUN AS EJECUCIONES
FROM QSYS2.SYSPLANSTAT
WHERE TOTAL_TIMES_RUN > 0
ORDER BY AVERAGE_TOTAL_TIME DESC
FETCH FIRST 20 ROWS ONLY;

-- Forzar que una query use SQE (si cae en CQE)
-- Reescribir evitando features solo-CQE como OPNQRYF

Visual Explain

Visual Explain es la herramienta principal para analizar planes de ejecucion en Db2 for i. Esta integrada en IBM i Access Client Solutions (ACS) y muestra graficamente como el optimizador procesa cada query.

Table Scan

Lectura secuencial completa de la tabla. Aparece cuando no hay indice util o la selectividad es baja.

Index Scan

Usa un indice para localizar filas. Mucho mas eficiente para filtros selectivos.

Index Only Access

Todas las columnas necesarias estan en el indice. No necesita acceder a la tabla base.

Hash Join

Construye una hash table con la tabla mas chica y la usa para buscar matches de la grande.

Nested Loop Join

Para cada fila de la tabla externa, busca matches en la interna via indice. Eficiente con indices.

Temporary Result

Crea tabla temporal intermedia. Puede indicar falta de indice adecuado o sort costoso.

PostgreSQL -- EXPLAIN ANALYZE

  • EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
  • Muestra tiempo real vs estimado por nodo
  • Seq Scan, Index Scan, Bitmap Heap Scan
  • Hash Join, Merge Join, Nested Loop
  • pgAdmin tiene Visual Explain integrado

IBM i -- Visual Explain

  • Integrado en ACS > Run SQL Scripts
  • Muestra costo estimado y filas estimadas
  • Table Scan, Index Scan, Index Only
  • Hash Join, Nested Loop, Sort
  • Index Advisor integrado en el mismo panel
CL -- PRTSQLINF para obtener plan sin ACS
/* Imprimir informacion SQL de un programa */
PRTSQLINF OBJ(MILIB/MIPGM) +
          OBJTYPE(*PGM)

/* Para service programs */
PRTSQLINF OBJ(MILIB/MISRVPGM) +
          OBJTYPE(*SRVPGM)

/* El output va al spool: WRKSPLFLT */
Tip: En ACS Run SQL Scripts, escribi tu query y presiona Ctrl+Shift+E para obtener Visual Explain sin ejecutarla. Los nodos rojos o con alto costo relativo son los principales candidatos a optimizacion.

Indices y estrategias de indexacion

Db2 for i soporta dos tipos principales de indices: binary radix tree (el clasico, equivalente a B-Tree) y encoded vector index (EVI, optimizado para baja cardinalidad y data warehousing).

Tipo de indiceUso idealEquivalente
Binary Radix TreeWHERE con igualdad o rangos, ORDER BY, JOIN.B-Tree en PostgreSQL/MySQL
Encoded Vector Index (EVI)Columnas de baja cardinalidad. Star joins.Bitmap Index en Oracle
Derived Key IndexIndices sobre expresiones (UPPER, YEAR).Expression Index en PostgreSQL
Sparse IndexIndice parcial: solo filas que cumplen condicion.Partial Index en PostgreSQL
SQL -- Crear diferentes tipos de indices
-- Indice radix tree clasico (equivalente a B-Tree)
CREATE INDEX MILIB.IDX_CLIENTES_NOMBRE
  ON MILIB.CLIENTES (NOMBRE);

-- Indice compuesto para queries frecuentes
CREATE INDEX MILIB.IDX_PEDIDOS_CLIFECHA
  ON MILIB.PEDIDOS (CLIENTE_ID, FECHA DESC);

-- Encoded Vector Index para columna de baja cardinalidad
CREATE ENCODED VECTOR INDEX MILIB.EVI_CLIENTES_REGION
  ON MILIB.CLIENTES (REGION);

CREATE ENCODED VECTOR INDEX MILIB.EVI_PEDIDOS_ESTADO
  ON MILIB.PEDIDOS (ESTADO);

-- Indice sobre expresion (derived key)
CREATE INDEX MILIB.IDX_CLIENTES_UPPER_NOMBRE
  ON MILIB.CLIENTES (UPPER(NOMBRE));

-- Indice parcial (sparse): solo filas activas
CREATE INDEX MILIB.IDX_CLIENTES_ACTIVOS
  ON MILIB.CLIENTES (NOMBRE, SALDO)
  WHERE ESTADO = 'A';

-- Indice INCLUDE: columnas extra para index-only access
CREATE INDEX MILIB.IDX_CLIENTES_EMAIL_INC
  ON MILIB.CLIENTES (EMAIL)
  INCLUDE (NOMBRE, TELEFONO);
SQL -- Index Advisor: recomendaciones de SQE
-- Ver indices recomendados por el optimizador
SELECT
  TABLE_SCHEMA,
  TABLE_NAME,
  INDEX_KEY AS COLUMNAS_SUGERIDAS,
  INDEX_INCLUDE_COLUMNS AS INCLUDE_COLS,
  TIMES_ADVISED AS VECES_RECOMENDADO,
  LAST_ADVISED AS ULTIMA_VEZ,
  ESTIMATED_CREATION_TIME AS TIEMPO_CREACION,
  REASON AS MOTIVO
FROM QSYS2.SYSIXADV
WHERE TABLE_SCHEMA = 'MILIB'
ORDER BY TIMES_ADVISED DESC;

-- Limpiar sugerencias acumuladas
-- CALL QSYS2.CLEAR_INDEX_ADVISOR('MILIB');
Regla general: No crear indices indiscriminadamente. Cada indice consume espacio y ralentiza INSERT/UPDATE/DELETE. Usa Index Advisor y Visual Explain para identificar cuales son realmente necesarios. Un indice con TIMES_ADVISED alto es un buen candidato.

Estadisticas y catalog

El optimizador SQE depende de estadisticas actualizadas para estimar costos con precision. Las estadisticas se almacenan en el catalog del sistema y se pueden consultar y refrescar.

SQL -- Consultar estadisticas del catalog
-- Estadisticas de tablas: filas, tamano, filas eliminadas
SELECT
  TABLE_SCHEMA, TABLE_NAME,
  ROW_COUNT AS FILAS,
  DATA_SIZE AS TAMANO_BYTES,
  NUMBER_DELETED_ROWS AS FILAS_ELIMINADAS,
  COLUMN_COUNT AS COLUMNAS
FROM QSYS2.SYSTABLESTAT
WHERE TABLE_SCHEMA = 'MILIB'
ORDER BY ROW_COUNT DESC;

-- Estadisticas de columnas: cardinalidad y distribucion
SELECT
  COLUMN_NAME,
  NUMBER_DISTINCT_VALUES AS VALORES_DISTINTOS,
  LOW_VALUE, HIGH_VALUE,
  AVG_LENGTH AS LARGO_PROMEDIO,
  NUMBER_NULLS AS NULOS
FROM QSYS2.SYSCOLUMNSTAT
WHERE TABLE_SCHEMA = 'MILIB'
  AND TABLE_NAME = 'CLIENTES'
ORDER BY NUMBER_DISTINCT_VALUES DESC;

-- Estadisticas de indices: uso y efectividad
SELECT
  INDEX_NAME, TABLE_NAME,
  COLUMN_NAMES,
  NUMBER_ROWS AS FILAS_INDICE,
  INDEX_SIZE AS TAMANO,
  QUERY_USE_COUNT AS VECES_USADO,
  LAST_QUERY_USE AS ULTIMO_USO
FROM QSYS2.SYSINDEXSTAT
WHERE TABLE_SCHEMA = 'MILIB'
ORDER BY QUERY_USE_COUNT DESC;
CL -- Actualizar estadisticas
/* Reorganizar tabla y actualizar estadisticas */
RGZPFM FILE(MILIB/CLIENTES) RBDACCPTH(*YES)

/* RBDACCPTH(*YES) reconstruye los access paths (indices)
   y actualiza las estadisticas del catalog */

/* Para solo actualizar estadisticas sin reorganizar */
CALL SYSPROC.AQPADVISE('MILIB', 'CLIENTES')
Estadisticas automaticas: IBM i actualiza estadisticas automaticamente durante la ejecucion de queries cuando detecta que estan desactualizadas. Para tablas con cambios masivos frecuentes, conviene forzar una actualizacion periodica despues de cargas batch.

Plan cache y SQL Plan Cache

El SQL Plan Cache almacena los planes de ejecucion compilados para reutilizarlos en ejecuciones posteriores. Analizar el plan cache es la forma mas efectiva de identificar las queries mas costosas del sistema.

SQL -- Analizar el plan cache
-- Top 20 queries mas costosas por tiempo total acumulado
SELECT
  AVERAGE_TOTAL_TIME AS TIEMPO_PROM_MS,
  TOTAL_TIMES_RUN AS EJECUCIONES,
  DECIMAL(AVERAGE_TOTAL_TIME * TOTAL_TIMES_RUN / 1000, 11, 1)
    AS COSTO_TOTAL_SEG,
  ROWS_RETURNED_AVERAGE AS FILAS_PROM,
  QUERY_OPTIMIZATION_ENGINE AS MOTOR,
  SUBSTR(STATEMENT_TEXT, 1, 200) AS SQL_TEXT
FROM QSYS2.SYSPLANSTAT
WHERE AVERAGE_TOTAL_TIME > 0
ORDER BY COSTO_TOTAL_SEG DESC
FETCH FIRST 20 ROWS ONLY;

-- Queries que usan table scan (candidatas a indice)
SELECT
  SUBSTR(STATEMENT_TEXT, 1, 200) AS SQL_TEXT,
  AVERAGE_TOTAL_TIME AS TIEMPO_MS,
  TOTAL_TIMES_RUN AS EJECUCIONES
FROM QSYS2.SYSPLANSTAT
WHERE PLAN_TEXT LIKE '%Table Scan%'
  AND TOTAL_TIMES_RUN > 10
ORDER BY AVERAGE_TOTAL_TIME DESC
FETCH FIRST 20 ROWS ONLY;

-- Crear snapshot persistente del plan cache
CALL QSYS2.DUMP_PLAN_CACHE(
  PLAN_CACHE_SCHEMA => 'MILIB'
);
Limpiar plan cache: Despues de crear indices o reorganizar tablas, puede ser necesario limpiar el plan cache para forzar la recompilacion. Usa CALL QSYS2.CLEAR_PLAN_CACHE() con cuidado en produccion: fuerza la recompilacion de todas las queries.

Query governor y limites

El query governor permite establecer limites de tiempo y recursos para queries SQL. Es esencial en produccion para evitar que queries mal escritas o ad-hoc consuman todos los recursos del sistema.

CL -- Configurar query governor
/* Limite de tiempo para queries del job actual */
CHGQRYA QRYTIMLMT(30)
/* Queries que excedan 30 segundos se cancelan */

/* Permitir paralelismo con limite */
CHGQRYA QRYTIMLMT(60) DEGREE(*MAX)

/* Aplicar QAQQINI de una biblioteca especifica */
CHGQRYA QRYOPTLIB(MILIB)
SQL -- Opciones de query en QAQQINI
-- Copiar QAQQINI a tu biblioteca para personalizarlo
-- CRTDUPOBJ OBJ(QAQQINI) FROMLIB(QSYS) OBJTYPE(*FILE) TOLIB(MILIB)

-- Ver opciones actuales
SELECT QQPARM AS PARAMETRO, QQVAL AS VALOR
FROM MILIB.QAQQINI
ORDER BY QQPARM;

-- Configuraciones comunes de performance
INSERT INTO MILIB.QAQQINI (QQPARM, QQVAL) VALUES
  ('QUERY_TIME_LIMIT', '120'),
  ('PARALLEL_DEGREE', 'ANY'),
  ('OPTIMIZE_FOR_N_ROWS', '100'),
  ('REOPTIMIZE_ACCESS_PLAN', '*YES'),
  ('FORCE_JOIN_ORDER', '*NO'),
  ('ALLOW_TEMPORARY_INDEXES', '*YES');

Encoding schemes y CCSID

El CCSID (Coded Character Set Identifier) define la codificacion de caracteres de tablas y columnas. Problemas de CCSID causan degradacion de performance por conversiones implicitas y pueden impedir el uso de indices.

CCSIDEncodingUso tipico
37EBCDIC US/CanadaDefault historico de IBM i
65535*HEX (sin conversion)Datos binarios, sin conversion automatica
1208UTF-8Aplicaciones web y modernas
1200UTF-16Aplicaciones Java, datos Unicode
284EBCDIC Spain/Latin AmericaDatos en espanol con acentos
SQL -- Verificar y gestionar CCSID
-- Ver CCSID de columnas de una tabla
SELECT
  COLUMN_NAME, DATA_TYPE, CCSID,
  CHARACTER_MAXIMUM_LENGTH
FROM QSYS2.SYSCOLUMNS
WHERE TABLE_SCHEMA = 'MILIB'
  AND TABLE_NAME = 'CLIENTES'
  AND DATA_TYPE IN ('CHAR', 'VARCHAR', 'CLOB')
ORDER BY ORDINAL_POSITION;

-- Crear tabla con CCSID UTF-8
CREATE TABLE MILIB.CLIENTES_UTF8 (
  ID INTEGER GENERATED ALWAYS AS IDENTITY,
  NOMBRE VARCHAR(200) CCSID 1208,
  EMAIL  VARCHAR(254) CCSID 1208
);
Performance warning: Cuando el CCSID de una columna difiere del CCSID de la columna de JOIN o del job, el sistema convierte automaticamente. Esto puede impedir el uso de indices y forzar table scans. Asegurate de que los CCSIDs sean consistentes entre tablas que se joinean frecuentemente.

Materialized Query Tables (MQT)

Las MQTs son tablas que almacenan resultados precomputed de queries complejas, similar a materialized views de PostgreSQL/Oracle. El optimizador SQE puede redirigir queries automaticamente a MQTs compatibles.

SQL -- Crear y usar MQTs
-- MQT de ventas mensuales preagregadas
CREATE TABLE MILIB.MQT_VENTAS_MENSUALES AS (
  SELECT
    YEAR(FECHA) AS ANIO,
    MONTH(FECHA) AS MES,
    REGION,
    CATEGORIA,
    COUNT(*) AS CANTIDAD_VENTAS,
    SUM(MONTO) AS TOTAL_VENTAS,
    AVG(MONTO) AS PROMEDIO_VENTA
  FROM MILIB.VENTAS
  GROUP BY YEAR(FECHA), MONTH(FECHA), REGION, CATEGORIA
) DATA INITIALLY DEFERRED REFRESH DEFERRED
  MAINTAINED BY USER;

-- Cargar/refrescar datos
REFRESH TABLE MILIB.MQT_VENTAS_MENSUALES;

-- SQE puede usar la MQT automaticamente para:
SELECT REGION, SUM(MONTO) AS TOTAL
FROM MILIB.VENTAS
WHERE YEAR(FECHA) = 2025
GROUP BY REGION;
-- El optimizador detecta que puede resolverlo
-- leyendo MQT_VENTAS_MENSUALES en vez de VENTAS
MQT routing: SQE detecta automaticamente cuando una query puede resolverse via una MQT existente. Esto es transparente para la aplicacion y se verifica con Visual Explain (aparece el nodo de la MQT en vez de la tabla base).

Proceso de tuning

El tuning de Db2 for i sigue un proceso sistematico que va desde identificar las queries problematicas hasta aplicar las optimizaciones con mayor impacto.

1Identificar queries lentas

Usar SQL Plan Cache (QSYS2.SYSPLANSTAT) para encontrar queries con mayor tiempo total acumulado (tiempo promedio x ejecuciones).

2Analizar con Visual Explain

Ejecutar Visual Explain en ACS para ver el plan grafico. Buscar table scans, sorts costosos y temporary results.

3Revisar Index Advisor

Consultar QSYS2.SYSIXADV para ver recomendaciones. Priorizar indices con TIMES_ADVISED alto.

4Crear indices necesarios

Crear los indices sugeridos. Verificar con Visual Explain que el plan mejoro. No crear indices redundantes.

5Actualizar estadisticas

Si estan desactualizadas, ejecutar RGZPFM con RBDACCPTH(*YES) para reorganizar y refrescar stats.

6Considerar rewrite de la query

Eliminar subqueries correlacionadas, usar CTEs, simplificar JOINs. A veces es la optimizacion mas efectiva.

Herramientas de analisis

HerramientaTipoUso principal
Visual Explain (ACS)GUIAnalisis grafico de planes de ejecucion
SQL Plan CacheSQL ViewsTop queries por costo/tiempo
Index AdvisorSQL ViewsRecomendaciones automaticas de indices
PRTSQLINFCL CommandInfo de SQL statements en programas
SQL Performance MonitorNavigatorMonitoreo en tiempo real de SQL
QAQQINIData AreaOpciones del optimizador por biblioteca
CHGQRYACL CommandAtributos de query por job
Db2 Health CenterNavigatorHealth checks automaticos de Db2
SQL -- Queries utiles para performance analysis
-- Indices no usados (candidatos a eliminar)
SELECT
  INDEX_NAME, TABLE_NAME, TABLE_SCHEMA,
  COLUMN_NAMES, INDEX_SIZE,
  QUERY_USE_COUNT, LAST_QUERY_USE
FROM QSYS2.SYSINDEXSTAT
WHERE TABLE_SCHEMA = 'MILIB'
  AND (QUERY_USE_COUNT = 0
       OR LAST_QUERY_USE < CURRENT_TIMESTAMP - 6 MONTHS)
ORDER BY INDEX_SIZE DESC;

-- Tablas con muchas filas eliminadas (necesitan RGZPFM)
SELECT
  TABLE_SCHEMA, TABLE_NAME,
  ROW_COUNT, NUMBER_DELETED_ROWS,
  DECIMAL(NUMBER_DELETED_ROWS * 100.0
    / NULLIF(ROW_COUNT + NUMBER_DELETED_ROWS, 0), 5, 1)
    AS PCT_ELIMINADAS
FROM QSYS2.SYSTABLESTAT
WHERE TABLE_SCHEMA = 'MILIB'
  AND NUMBER_DELETED_ROWS > 0
ORDER BY PCT_ELIMINADAS DESC;

-- Locks activos sobre objetos de tu biblioteca
SELECT
  JOB_NAME, LOCK_STATE,
  LOCK_OBJECT_NAME, LOCK_OBJECT_LIBRARY,
  LOCK_TYPE
FROM QSYS2.OBJECT_LOCK_INFO
WHERE LOCK_OBJECT_LIBRARY = 'MILIB'
ORDER BY LOCK_STATE;
Workflow recomendado: (1) Revisar SQL Plan Cache semanalmente para detectar queries degradadas. (2) Ejecutar Index Advisor y crear indices necesarios. (3) Reorganizar tablas con alto porcentaje de filas eliminadas. (4) Verificar estadisticas actualizadas. Automatiza estos pasos con scheduled jobs.

Adaptive Query Processing mejorado (7.6)

IBM i 7.6 mejora significativamente Adaptive Query Processing (AQP), el sistema que permite al query engine aprender de ejecuciones anteriores y ajustar los planes de acceso automaticamente.

Mejoras en 7.6

  • Mejor deteccion de cambios en distribucion de datos que invalidan planes existentes
  • Ajuste automatico de join strategies basado en cardinalidades reales vs estimadas
  • Retroalimentacion mejorada al plan cache cuando AQP detecta mejores alternativas
  • Reduccion de overhead en el proceso de learning del optimizer

Query engine optimizations

  • Mejoras generales de rendimiento en el motor de queries
  • Optimizaciones para PARALLEL DEGREE en UPDATE/DELETE con FINAL TABLE
  • Mejor aprovechamiento de indices en queries complejas con multiples joins
SQL — Verificar actividad AQP
-- Ver queries donde AQP intervino con sugerencias
SELECT QQRID, QQJFLD, QQ1000,
       QQRCOD AS REASON_CODE
FROM QSYS2.SYSPLANSTAT
WHERE QQ1000 LIKE '%AQP%'
ORDER BY QQRID DESC
FETCH FIRST 20 ROWS ONLY;

SELF: SQL Error Logging Facility (7.6)

SELF (SQL Logging Error Facility) es una herramienta de diagnostico que registra errores SQL y proporciona informacion detallada para resolver problemas de queries. En IBM i 7.6, SELF recibe mejoras significativas y una extension para VS Code.

Capacidades de SELF

  • Registro automatico de errores SQL con contexto completo (query, programa, timestamp)
  • Clasificacion de errores por severidad y tipo
  • Integracion con la extension Db2 for i de VS Code para visualizacion inline
  • Consulta de errores via SQL Services en QSYS2

SELF en VS Code

La extension Db2 for i de VS Code integra SELF directamente en el editor: los errores SQL se muestran inline con su causa raiz y sugerencias de correccion, similar a como un linter muestra errores de sintaxis en codigo.

Workflow recomendado: Combinar Visual Explain (para analisis de plan), AQP (para optimizacion automatica) y SELF (para diagnostico de errores) como las tres herramientas de performance de IBM i 7.6.