Visual Explain, SQL Query Engine (SQE), estrategias de indexacion, query governor, estadisticas del catalog, plan cache y proceso de tuning.
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
SQE -- SQL Query Engine
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.
-- 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 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
IBM i -- Visual Explain
/* 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 */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).
-- 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);
-- 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');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.
-- 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;
/* 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')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.
-- 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'
);CALL QSYS2.CLEAR_PLAN_CACHE() con cuidado en produccion: fuerza la recompilacion de todas las queries.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.
/* 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)
-- 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');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.
37EBCDIC US/CanadaDefault historico de IBM i65535*HEX (sin conversion)Datos binarios, sin conversion automatica1208UTF-8Aplicaciones web y modernas1200UTF-16Aplicaciones Java, datos Unicode284EBCDIC Spain/Latin AmericaDatos en espanol con acentos-- 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
);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.
-- 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 VENTASEl 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.
-- 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;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.
-- 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 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.
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.