Error: Too many Contexts of Relation/Procedure/Views. Maximum allowed is 255

1 comentario

Cuando escribes un stored procedure, un execute block, o una view hay un límite a la cantidad de contextos que puedes tener.

¿Qué es un contexto?

Es cualquier comando del Firebird que hace referencia a una tabla (o más de una).

Ese límite es por declaración, no por stored procedure, execute block o view. No hay que confundir.

Las declaraciones posibles son:

  • INSERT
  • UPDATE
  • DELETE
  • SELECT

Ninguna de esas declaraciones puede tener más de 255 contextos. Ese es el límite máximo.

Pero además, cada GROUP BY y cada UNION suma 1 contexto más.

Ejemplos:

  • Un SELECT a una tabla tiene un contexto
  • Un SELECT con un JOIN tiene dos contextos
  • Un SELECT con dos JOIN tiene tres contextos
  • Un SELECT con una subconsulta tiene dos contextos
  • Un SELECT con dos subconsultas tiene tres contextos
  • Un SELECT con una subconsulta y con un JOIN tiene tres contextos
  • Un SELECT autoreferenciado (ambas tablas del JOIN son la misma) tiene dos contextos
  • Un SELECT con un GROUP BY tiene dos contextos
  • Un INSERT que referencia a una view, tiene un contexto por cada tabla que es referenciada en la view
  • Un UPDATE que referencia a una view, tiene un contexto por cada tabla que es referenciada en la view

Cada INSERT, cada UPDATE y cada DELETE suma 2, por las seudo-variables NEW y OLD de los triggers. En los demás casos se suma 1.

La cantidad máxima de contextos que puedes tener (hasta la versión Firebird 3.0) es de 255, si superas esa cantidad entonces verás el mensaje de error que da título a este artículo.

Listado 1.


SELECT <mis_columnas_1> FROM MiTabla1;

UNION

SELECT <mis_columnas_2> FROM MiTabla2;

En el Listado 1. hay 3 contextos.

Listado 2.


INSERT INTO MiTabla <mis_columnas> VALUES <lista_valores>;

INSERT INTO MiTabla <mis_columnas> VALUES <otros_valores>;

En el Listado 2. el primer INSERT tiene 2 contextos y el segundo INSERT también tiene 2 contextos. Esos contextos no se suman, cada uno de ellos es independiente, porque lo que vale es la cantidad de contextos que cada declaración tiene.

Solución:

Si el Firebird te muestra este mensaje de error entonces tienes que disminuir la cantidad de contextos. Eso puedes hacerlo de varias maneras, por ejemplo usando una tabla CTE en lugar de varias subconsultas, o cambiando la condición de filtro del WHERE, o disminuyendo la cantidad de JOIN, etc.

Artículos relacionados:

El índice del blog Firebird21

El foro del blog Firebird21

Entendiendo WITH LOCK

2 comentarios

Este artículo es para expertos solamente.

Como seguramente ya sabes, en Firebird lo preferible y lo recomendable es que las transacciones sean optimistas. Sin embargo hay algunos casos en que se necesita que una transacción sea pesimista.

Estos casos son rarísimos y si los necesitas con frecuencia entonces evidentemente aún no has entendido como funciona Firebird.

Hay una cláusula del comando SELECT que te permite tener transacciones pesimistas pero si no entiendes muy pero muy bien como funcionan las transacciones en Firebird, no deberías usar esa cláusula jamás.

Este artículo es para expertos solamente.

Sintaxis

SELECT
   ...
FROM
   MiTabla
[WHERE
   MiCondición]
[FOR UPDATE [OF...]]
[WITH LOCK]

WITH LOCK realiza un bloqueo pesimista a la fila (o filas) del conjunto resultado del SELECT. Lo recomendable es que:

  1. La cantidad de filas bloqueadas sea muy pequeña (lo ideal es que sea una sola fila)
  2. La aplicación controle muy bien el bloqueo

Si usas WITH LOCK a veces conseguirás que las filas sean bloqueadas y a veces no. Si otra transacción ha previamente bloqueado a alguna de esas filas entonces WITH LOCK fallará.

Si WITH LOCK tuvo éxito entonces ninguna otra transacción podrá hacerle un UPDATE ni un DELETE a las filas involucradas, hasta que la transacción que hizo el WITH LOCK finalice. Por eso el bloqueo es pesimista.

Cuando se incluye la cláusula FOR UPDATE el bloqueo es aplicado a cada fila, una por una, a medida que son extraídas por el Servidor. Es por lo tanto posible (aunque muy improbable, pero posible) que un bloqueo que pareció tener éxito cuando se hizo el SELECT falle más adelante cuando se quiere hacer el UPDATE. ¿Por qué? Porque otra transacción concurrente bloqueó a esa fila e hizo un COMMIT antes de que el SELECT … WITH LOCK finalizara.

Esa es otra razón más para que el SELECT … WITH LOCK involucre a muy pocas filas (idealmente, a una sola fila). De esta manera se minimizará el riesgo.

Un SELECT … WITH LOCK te garantiza que:

  1. Todas las filas del conjunto resultado que obtuviste están bloqueadas
  2. Todas las demás filas no están bloqueadas

Un SELECT … WITH LOCK no te puede garantizar que:

  1. Todas las filas que cumplen con la condición del filtro del WHERE estén bloqueadas

Entendamos mejor este último punto.

Supongamos que si escribes tu SELECT … WHERE obtienes 100 filas, pero al escribir SELECT … WHERE … WITH LOCK obtienes 98 filas. Eso podría ocurrir si otra transacción concurrente ha bloqueado a las 2 filas faltantes entre el inicio y el fin del SELECT … WITH LOCK. Entonces el SELECT … WITH LOCK te garantiza que hay 98 filas bloqueadas pero no te garantiza que no hay filas que cumplen la condición de filtro del WHERE pero que no fueron bloqueadas (en este ejemplo, hay 2 filas que cumplen la condición del WHERE pero no están bloqueadas).

¿Recuerdas que lo ideal es que el SELECT … WITH LOCK devuelva una sola fila? Ahora se entiende mejor el motivo.

Bloqueo de filas

El SELECT … WITH LOCK realiza (si es que puede) un bloqueo pesimista pero siempre cumpliendo con los parámetros de las transacciones.

Eso significa que si el modo de bloqueo de la transacción donde se encuentra el SELECT … WITH LOCK es WAIT y otra transacción previamente ha bloqueado a una fila de su conjunto resultado, esperará hasta que la otra transacción finalice. Si su modo de bloqueo es NO WAIT entonces inmediatamente finalizará con error, y así con los demás parámetros.

¿Dónde no se puede usar SELECT … WITH LOCK?

  • En una subconsulta
  • En un JOIN
  • Con el operador DISTINCT
  • Con la cláusula GROUP BY
  • Con las funciones agregadas: COUNT(), SUM(), AVG(), MAX(), MIN(), LIST()
  • En una vista
  • En la salida de un stored procedure seleccionable
  • En una tabla externa

Conclusión:

En Firebird muy raramente se necesita que una transacción sea pesimista pero si ese es el caso hay varias maneras de conseguirlo, una de ellas es mediante el comando SELECT … WITH LOCK.

Si se usa este comando hay que tratar de que el conjunto resultado tenga muy pocas filas, idealmente una sola fila.

Si la condición de filtro de la cláusula WHERE cumple más de una fila, no se puede garantizar que el SELECT … WITH LOCK haya bloqueado a todas esas filas porque otra transacción concurrente podría haber realizado algún bloqueo.

Artículos relacionados:

Entendiendo a las transacciones

Evitando confilctos en las transacciones

Evitando actualizaciones concurrentes del mismo registro

Transacciones optimistas y transacciones pesimistas

Algo más sobre transacciones optimistas y transacciones pesimistas

¿Por qué en Firebird es preferible que las transacciones sean optimistas?

El índice del blog Firebird21

El foro del blog Firebird21