1. TUNE TABLE para recopilar estadísticas de tabla
Puntos Clave
- Propósito: Recopila estadísticas de tabla (selectividad, tamaño, valores atípicos) usadas por el optimizador de consultas SQL
- $SYSTEM.SQL.TuneTable(): Método programático para ajustar una tabla específica
- Comando SQL TUNE TABLE: Alternativa SQL DDL para recopilar estadísticas
- Selectividad: Porcentaje de filas que un valor dado selecciona; menor selectividad = índice más selectivo
- Valores atípicos: Valores frecuentes que sesgan la selectividad (ej., "USA" en la columna Country)
- Cuándo re-ajustar: Después de cambios significativos en los datos (cargas masivas, eliminaciones, cambios en la distribución de datos)
Notas Detalladas
Descripción general
El optimizador de consultas SQL de InterSystems IRIS usa estadísticas de tabla para generar planes de consulta eficientes. Sin estadísticas precisas, el optimizador puede elegir estrategias de acceso subóptimas (ej., escaneo completo de tabla en vez de usar un índice disponible). TUNE TABLE recopila estas estadísticas muestreando los datos y calculando selectividad, tamaño promedio de campo y tamaño del extent.
Ejecutar TUNE TABLE
// Enfoque programático
DO $SYSTEM.SQL.TuneTable("MyApp.Patient")
// Con salida detallada
DO $SYSTEM.SQL.TuneTable("MyApp.Patient", 1)
// Ajustar todas las tablas en el namespace
DO $SYSTEM.SQL.TuneAllTables()
-- Enfoque SQL
TUNE TABLE MyApp.Patient
-- Ajustar y actualizar la definición de clase con estadísticas
TUNE TABLE MyApp.Patient %SAVE_VALUES
Qué calcula TUNE TABLE
1. Tamaño del Extent: Número total de filas en la tabla. El optimizador lo usa para estimar tamaños de resultados de consultas y elegir entre acceso por índice y escaneo de tabla.
2. Selectividad: Para cada propiedad/columna, el porcentaje de filas que un solo valor típicamente selecciona. Calculado como 1/número_de_valores_distintos * 100:
// Ejemplo: tabla Patient con 10,000 filas
// Columna State tiene 50 valores distintos
// Selectividad = 1/50 * 100 = 2%
// Esto significa que cualquier valor de estado selecciona ~2% de las filas
// Columna SSN tiene 10,000 valores distintos (único)
// Selectividad = 1/10000 * 100 = 0.01%
// Muy selectivo - ideal para acceso basado en índice
3. Valores atípicos: Valores que aparecen significativamente más a menudo de lo que la selectividad promedio sugiere. El optimizador usa la información de valores atípicos para generar diferentes planes dependiendo del valor del parámetro:
// Columna Country: 90% de las filas tienen "USA", 10% tienen otros países
// La selectividad promedio sugiere que cada país selecciona ~10% de las filas
// Pero "USA" realmente selecciona 90% - es un valor atípico
// Para "USA", un escaneo de tabla es mejor; para "France", un índice es mejor
Interpretar estadísticas en la definición de clase
Después de TUNE TABLE con %SAVE_VALUES, las estadísticas se almacenan en la definición de clase:
// Ver como parámetro de clase o metadatos de almacenamiento
// Selectividad almacenada por propiedad:
Property State As %String [ SqlSelectivity = "2%" ];
Property SSN As %String [ SqlSelectivity = "0.01%" ];
Property Country As %String [ SqlSelectivity = "10%",
SqlOutlierSelectivity = "90%",
SqlOutlierValue = "USA" ];
Cuándo re-ajustar
- Después de importaciones masivas de datos que cambien significativamente el conteo de filas
- Después de operaciones DELETE a gran escala
- Cuando la distribución de datos cambia (ej., se agregan nuevas regiones geográficas)
- Después de la carga inicial de datos (datos de producción vs datos de desarrollo)
- Cuando el rendimiento de consultas se degrada inesperadamente
- Mejor práctica: Purgar consultas en caché después del ajuste para que el optimizador use las nuevas estadísticas
// Flujo completo de optimización
DO $SYSTEM.SQL.TuneTable("MyApp.Patient", 1)
DO $SYSTEM.SQL.PurgeForTable("MyApp.Patient")
Referencias de Documentación
2. Interpretar planes de consulta básicos
Puntos Clave
- Show Plan: Muestra la estrategia de ejecución de consulta elegida por el optimizador
- Métodos de acceso: Escaneo de tabla (leer todas las filas) vs escaneo de índice (leer índice luego datos) vs acceso solo por índice
- Estimaciones de costo: Los números de costo relativo ayudan a comparar formulaciones alternativas de consultas
- Indicadores de escaneo completo de tabla: "Read master map" o escaneo secuencial del global de datos
- Indicadores de uso de índice: "Read index map" seguido de búsqueda en mapa de datos
- Estrategias de join: Nested loop join, merge join, hash join - elegidos según tamaños de tabla e índices
Notas Detalladas
Descripción general
El plan de consulta (también llamado plan de ejecución o Show Plan) revela cómo el optimizador SQL pretende ejecutar una consulta. Leer planes de consulta es esencial para diagnosticar consultas lentas, validar que los índices se están usando y entender las características de rendimiento de consultas.
Acceder al plan de consulta
Vía Portal de Administración: Navegar a System Explorer > SQL, ingresar una consulta y hacer clic en Show Plan (sin ejecutar).
Vía SQL EXPLAIN:
EXPLAIN SELECT Name, City FROM MyApp.Patient WHERE City = 'Boston'
La sentencia EXPLAIN devuelve el plan de consulta sin ejecutar la consulta. Muestra la estrategia de acceso, uso de índices y decisiones de optimización tomadas por el motor SQL.
Vía SQL Dinámico (%Display() y %GetImplementationDetails() muestran solo el nombre de la clase de implementación y el texto de la sentencia, no el plan de consulta):
SET stmt = ##class(%SQL.Statement).%New()
SET sc = stmt.%Prepare("SELECT Name, City FROM MyApp.Patient WHERE City = ?")
// Muestra clase de implementación y texto de la sentencia (NO el plan de consulta)
DO stmt.%Display()
// Obtiene nombre de clase y texto SQL programáticamente (NO el plan de consulta)
SET ok = stmt.%GetImplementationDetails(.className, .text)
Para ver el plan de consulta real programáticamente, use EXPLAIN vía SQL Dinámico:
SET stmt = ##class(%SQL.Statement).%New()
SET sc = stmt.%Prepare("EXPLAIN SELECT Name, City FROM MyApp.Patient WHERE City = ?")
SET rs = stmt.%Execute()
WHILE rs.%Next() {
WRITE rs.%GetData(1), !
}
Leer un plan de consulta
Un plan de consulta típico muestra los pasos que el motor tomará:
Read index map MyApp.Patient.CityIdx,
looping on City = ?,
using the given %SQLUPPER value.
For each row:
Read master map MyApp.Patient.Default,
using the ID found in the index map.
Output the row.
Elementos clave a identificar:
1. Escaneo de índice ("Read index map"): La consulta usa un índice para encontrar IDs de fila coincidentes. Esto es eficiente para consultas selectivas.
2. Lectura de mapa maestro ("Read master map"): Después de encontrar IDs del índice, el motor lee los datos reales. Esta es una búsqueda por ID, que es rápida.
3. Escaneo completo de tabla ("Read master map ... looping on ID"): Si ve un bucle secuencial sobre el mapa maestro sin referencia a un índice, significa que el optimizador eligió un escaneo de tabla.
// Escaneo completo de tabla - sin índice usado
Read master map MyApp.Patient.Default,
looping on ID.
For each row:
Check WHERE condition: City = ?.
If true, output the row.
4. Acceso solo por índice: Cuando todas las columnas necesarias están en el índice, no se necesita lectura del mapa maestro:
// Solo índice - muy eficiente
Read index map MyApp.Patient.NameCityIdx,
looping on Name, City.
Output Name and City directly from the index.
Comparar planes para optimización
// Consulta sin índice en City - escaneo completo de tabla
// SELECT * FROM MyApp.Patient WHERE City = 'Boston'
// Plan muestra: Read master map, looping on ID, check City = 'Boston'
// Costo: proporcional al total de filas en la tabla
// Después de agregar índice en City:
// Plan muestra: Read index map CityIdx WHERE City = 'Boston', then read master map by ID
// Costo: proporcional a las filas en Boston solamente
// Después de agregar índice compuesto en (City, Name):
// SELECT City, Name FROM MyApp.Patient WHERE City = 'Boston'
// Plan muestra: Read index map CityNameIdx - acceso solo por índice
// Costo: el más bajo - no se necesita acceso al mapa maestro
Estimaciones de costo
El optimizador asigna valores de costo relativo a diferentes alternativas de plan:
- Menor costo = se espera que sea más rápido
- Los costos son estimaciones basadas en estadísticas de tabla (de TUNE TABLE)
- Sin estadísticas, el optimizador puede tomar malas decisiones
- Importante: Los valores de costo son relativos y solo pueden compararse significativamente entre diferentes planes para la misma consulta sobre las mismas tablas. Comparar costos entre diferentes consultas o diferentes tablas no es significativo
Señales de alerta comunes de rendimiento en planes
- Escaneo completo de tabla en una tabla grande cuando existe un índice (índice faltante o estadísticas obsoletas)
- Creación de archivo temporal para ordenamiento (considerar agregar un índice en la columna ORDER BY)
- Nested loop join con una tabla interior grande (puede necesitar un índice en la columna de join)
- Función
%SQLUPPERpreviniendo el uso de índice (desajuste de sensibilidad a mayúsculas)
Referencias de Documentación
3. Probar corrección y rendimiento del código
Puntos Clave
- Framework %UnitTest: Framework de pruebas integrado estilo xUnit para ObjectScript
- $ZH (o $ZHOROLOG): Marca de tiempo de alta precisión para medir tiempo de ejecución
- Estructura de clase de prueba: Extender `%UnitTest.TestCase`, métodos con prefijo `Test`
- Aserciones: `$$$AssertEquals`, `$$$AssertTrue`, `$$$AssertStatusOK`, `$$$AssertNotEquals`
- Pruebas de rendimiento: Usar `$ZH` antes y después de bloques de código para medir tiempo transcurrido
- Pruebas no funcionales: Pruebas de carga, pruebas de estrés, benchmarks de tiempo de respuesta
Notas Detalladas
Descripción general
Las pruebas en InterSystems IRIS abarcan tanto pruebas funcionales (¿el código produce resultados correctos?) como pruebas no funcionales (¿tiene un rendimiento adecuado?). El framework %UnitTest proporciona pruebas funcionales estructuradas, mientras que $ZH y herramientas relacionadas permiten la medición de rendimiento.
Framework %UnitTest
La clase %UnitTest.TestCase proporciona un framework de pruebas estructurado. Las clases de prueba extienden %UnitTest.TestCase, y los métodos de prueba tienen el prefijo Test:
Class MyApp.Tests.PatientTest Extends %UnitTest.TestCase
{
Method TestCreatePatient()
{
SET patient = ##class(MyApp.Patient).%New()
SET patient.Name = "Test Patient"
SET patient.SSN = "999-99-9999"
// Asegurar que el objeto fue creado
DO $$$AssertNotEquals(patient, "", "Patient object created")
// Guardar y asegurar éxito
SET sc = patient.%Save()
DO $$$AssertStatusOK(sc, "Patient saved successfully")
// Verificar que se asignó ID
SET id = patient.%Id()
DO $$$AssertTrue(id > 0, "Patient ID assigned")
// Verificar recuperación
SET loaded = ##class(MyApp.Patient).%OpenId(id)
DO $$$AssertEquals(loaded.Name, "Test Patient", "Name matches")
DO $$$AssertEquals(loaded.SSN, "999-99-9999", "SSN matches")
// Limpieza
SET sc = ##class(MyApp.Patient).%DeleteId(id)
DO $$$AssertStatusOK(sc, "Patient deleted")
}
Method TestSSNUniqueness()
{
SET p1 = ##class(MyApp.Patient).%New()
SET p1.Name = "Patient 1"
SET p1.SSN = "111-11-1111"
SET sc = p1.%Save()
DO $$$AssertStatusOK(sc, "First patient saved")
SET p2 = ##class(MyApp.Patient).%New()
SET p2.Name = "Patient 2"
SET p2.SSN = "111-11-1111" // SSN duplicado
SET sc = p2.%Save()
DO $$$AssertStatusNotOK(sc, "Duplicate SSN rejected")
// Limpieza
DO ##class(MyApp.Patient).%DeleteId(p1.%Id())
}
/// Setup se ejecuta antes de cada método de prueba
Method OnBeforeOneTest(testname As %String) As %Status
{
// Inicializar datos de prueba
RETURN $$$OK
}
/// Teardown se ejecuta después de cada método de prueba
Method OnAfterOneTest(testname As %String) As %Status
{
// Limpiar datos de prueba
RETURN $$$OK
}
}
Ejecutar pruebas unitarias
// Ejecutar todas las pruebas en un directorio
DO ##class(%UnitTest.Manager).RunTest("MyApp/Tests")
// Ejecutar una clase de prueba específica
DO ##class(%UnitTest.Manager).RunTest("MyApp/Tests", "/run:MyApp.Tests.PatientTest")
// Ejecutar con salida de depuración
DO ##class(%UnitTest.Manager).DebugRunTestCase("", "MyApp.Tests.PatientTest")
Macros de aserción clave
| Macro | Propósito | Ejemplo |
|---|---|---|
$$$AssertTrue(expr, desc) | La expresión evalúa a verdadero | $$$AssertTrue(x>0, "Positive") |
$$$AssertEquals(val1, val2, desc) | Dos valores son iguales | $$$AssertEquals(name, "John", "Name check") |
$$$AssertNotEquals(val1, val2, desc) | Dos valores difieren | $$$AssertNotEquals(id, "", "ID assigned") |
$$$AssertStatusOK(sc, desc) | %Status es éxito | $$$AssertStatusOK(sc, "Save OK") |
$$$AssertStatusNotOK(sc, desc) | %Status es error | $$$AssertStatusNotOK(sc, "Expected failure") |
Pruebas de rendimiento con $ZH
$ZH (o $ZHOROLOG) devuelve una marca de tiempo de alta precisión en segundos (con fracciones de segundo) desde el inicio del sistema. Es ideal para medir tiempo transcurrido:
// Medición básica de tiempo
SET start = $ZH
// ... código a medir ...
SET elapsed = $ZH - start
WRITE "Elapsed time: ", elapsed, " seconds", !
// Medir tiempo de una consulta
SET start = $ZH
&sql(SELECT COUNT(*) INTO :cnt FROM MyApp.Patient WHERE City = 'Boston')
SET queryTime = $ZH - start
WRITE "Query time: ", queryTime, " seconds", !
WRITE "Rows: ", cnt, !
// Prueba comparativa de rendimiento
SET start1 = $ZH
FOR i = 1:1:10000 {
SET x = $PIECE("A^B^C^D^E", "^", 3)
}
SET time1 = $ZH - start1
SET start2 = $ZH
FOR i = 1:1:10000 {
SET x = $LISTGET($LISTBUILD("A","B","C","D","E"), 3)
}
SET time2 = $ZH - start2
WRITE "$PIECE: ", time1, " seconds", !
WRITE "$LIST: ", time2, " seconds", !
WRITE "Ratio: ", $FN(time1/time2, "", 2), !
Patrones de pruebas no funcionales
// Prueba de carga: medir rendimiento
SET start = $ZH
SET operations = 0
FOR i = 1:1:1000 {
SET patient = ##class(MyApp.Patient).%New()
SET patient.Name = "Load Test " _ i
SET sc = patient.%Save()
IF $$$ISOK(sc) SET operations = operations + 1
}
SET elapsed = $ZH - start
WRITE "Throughput: ", $FN(operations/elapsed, "", 0), " operations/second", !
// Benchmark de tiempo de respuesta
SET iterations = 100
SET totalTime = 0
FOR i = 1:1:iterations {
SET start = $ZH
SET obj = ##class(MyApp.Patient).%OpenId(1)
SET totalTime = totalTime + ($ZH - start)
}
WRITE "Average open time: ", $FN(totalTime/iterations*1000, "", 3), " ms", !
$ZH vs $HOROLOG
- $HOROLOG ($H): Devuelve fecha y hora en formato
días,segundos; solo precisión de segundos - $ZHOROLOG ($ZH): Devuelve segundos de alta precisión transcurridos desde el inicio del sistema; ideal para medición de rendimiento
- Para medición de tiempo: Siempre usar
$ZH- proporciona precisión sub-milisegundo
Referencias de Documentación
Resumen de Preparación para el Examen
Conceptos críticos a dominar:
- Propósito de TUNE TABLE: Recopila estadísticas de selectividad y tamaño del extent para el optimizador de consultas
- Selectividad: Menor porcentaje = más selectivo = mejor candidato para acceso basado en índice
- Valores atípicos: Valores comúnmente ocurrentes que requieren manejo especial por el optimizador
- Lectura de planes de consulta: Distinguir entre escaneo completo de tabla, escaneo de índice y acceso solo por índice
- $ZH para medición de tiempo: Variable de medición de alta precisión; siempre usar para medición de rendimiento, no $H
- Básicos de %UnitTest: Estructura de clase de prueba, macros de aserción, ciclo de vida setup/teardown
- Flujo TUNE TABLE + Purge: Siempre purgar consultas en caché después del ajuste para que las nuevas estadísticas tomen efecto
Escenarios comunes de examen:
- Explicar por qué una consulta es lenta (estadísticas faltantes, necesita TUNE TABLE)
- Leer un plan de consulta e identificar si se está usando un índice
- Elegir entre $ZH y $H para medición de rendimiento (siempre $ZH)
- Escribir una prueba unitaria con aserciones apropiadas para un escenario dado
- Identificar cuándo re-ajustar tablas (después de cargas masivas, cambios en distribución de datos)
- Interpretar valores de selectividad para predecir la efectividad del índice
- Reconocer indicadores de escaneo completo de tabla en un plan de consulta
Recomendaciones de práctica:
- Ejecutar TUNE TABLE en una tabla e inspeccionar los valores de selectividad almacenados en la definición de clase
- Comparar planes de consulta antes y después de agregar un índice - observar el cambio de escaneo de tabla a escaneo de índice
- Usar $ZH para medir el tiempo de varias operaciones: acceso a objetos, consultas SQL, operaciones con cadenas
- Escribir pruebas unitarias usando todas las macros de aserción principales
- Crear una tabla con datos atípicos (ej., 90% un valor) y observar cómo TUNE TABLE lo reporta
- Ejecutar Show Plan en el Portal de Administración para varios tipos de consulta (SELECT, JOIN, subconsulta)
- Comparar planes de consulta antes y después de TUNE TABLE para ver cómo las estadísticas afectan las decisiones del optimizador
- Medir el impacto en rendimiento de purgar consultas en caché (primera ejecución vs subsiguientes)