2011-12-16 46 views
14

Estoy tratando de optimizar una consulta compleja en PostgreSQL 9.1.2, que llama a algunas funciones. Estas funciones están marcadas como ESTABLE o INMUTABLE y se invocan varias veces con los mismos argumentos en la consulta. Supuse que PostgreSQL sería lo suficientemente inteligente como para llamarlos solo una vez por cada conjunto de entradas; después de todo, ese es el punto de ESTABLE e INMUTABLE, ¿no? Pero parece que las funciones se están llamando varias veces. Escribí una función simple para probar esto, que lo confirma:¿Por qué PostgreSQL llama a mi función STABLE/IMMUTABLE varias veces?

CREATE OR REPLACE FUNCTION test_multi_calls1(one integer) 
RETURNS integer 
AS $BODY$ 
BEGIN 
    RAISE NOTICE 'Called with %', one; 
    RETURN one; 
END; 
$BODY$ LANGUAGE plpgsql IMMUTABLE; 


WITH data AS 
(
    SELECT 10 AS num 
    UNION ALL SELECT 10 
    UNION ALL SELECT 20 
) 
SELECT test_multi_calls1(num) 
FROM data; 

Salida:

NOTICE: Called with 10 
NOTICE: Called with 10 
NOTICE: Called with 20 

por qué sucede esto y cómo puedo conseguir que sólo se ejecute la función de una vez?

Respuesta

23

La siguiente extensión de su código de prueba es de carácter informativo:

CREATE OR REPLACE FUNCTION test_multi_calls1(one integer) 
RETURNS integer 
AS $BODY$ 
BEGIN 
    RAISE NOTICE 'Immutable called with %', one; 
    RETURN one; 
END; 
$BODY$ LANGUAGE plpgsql IMMUTABLE; 
CREATE OR REPLACE FUNCTION test_multi_calls2(one integer) 
RETURNS integer 
AS $BODY$ 
BEGIN 
    RAISE NOTICE 'Volatile called with %', one; 
    RETURN one; 
END; 
$BODY$ LANGUAGE plpgsql VOLATILE; 

WITH data AS 
(
    SELECT 10 AS num 
    UNION ALL SELECT 10 
    UNION ALL SELECT 20 
) 
SELECT test_multi_calls1(num) 
FROM data 
where test_multi_calls2(40) = 40 
and test_multi_calls1(30) = 30 

SALIDA:

NOTICE: Immutable called with 30 
NOTICE: Volatile called with 40 
NOTICE: Immutable called with 10 
NOTICE: Volatile called with 40 
NOTICE: Immutable called with 10 
NOTICE: Volatile called with 40 
NOTICE: Immutable called with 20 

Aquí podemos ver que mientras que en la lista de selección de la función inmutable fue llamado varias veces, en la cláusula where se llamó una vez, mientras que la volátil se llamó tres veces.

Lo importante no es que PostgreSQL sólo llamar a una función o STABLEIMMUTABLE una vez con los mismos datos - el ejemplo muestra claramente que este no es el caso - es que puede llamar una sola vez. O tal vez lo llame dos veces cuando tenga que llamar a una versión volátil 50 veces, y así sucesivamente.

Existen diferentes formas de aprovechar la estabilidad y la inmutabilidad, con diferentes costos y beneficios. Para proporcionar el tipo de ahorro que está sugiriendo que debería hacer con listas de selección, tendría que almacenar en caché los resultados y luego buscar cada argumento (o lista de argumentos) en este caché antes de devolver el resultado en caché o la función de llamada en un caché -perder. Esto sería más costoso que llamar a su función, incluso en el caso de que hubiera un alto porcentaje de aciertos de caché (podría haber 0% de aciertos de caché, lo que significa que esta "optimización" hizo un trabajo adicional sin ningún beneficio). Podría almacenar quizás solo el último parámetro y resultado, pero nuevamente eso podría ser completamente inútil.

Esto es especialmente así, teniendo en cuenta que las funciones estables e inmutables son a menudo las funciones más ligeras.

Con la cláusula where sin embargo, la inmutabilidad de test_multi_calls1 permite que PostgreSQL realidad reestructurar la consulta desde el sentido corriente del SQL dada:

Para cada fila calcular test_multi_calls1 (30) y si el resultado es igual a 30 continuar con la tramitación de la fila en cuestión

para un plan de consulta completamente diferente:

Calcular test_multi_calls1 (30) y si es igual a 30 entonces continuar con la consulta devolverían una configuración de resultados de fila cero sin cualquier otro cálculo

Este es el tipo de uso que PostgreSQL hace de estable y INMUTABLE: no el almacenamiento en caché de los resultados, sino la reescritura de las consultas en diferentes consultas que son más eficientes pero dan los mismos resultados.

Tenga en cuenta también que test_multi_calls1 (30) se llama before test_multi_calls2 (40) sin importar el orden en que aparecen en la cláusula where. Esto significa que si la primera llamada no devuelve filas (reemplace = 30 con = 31 para probar), la función volátil no se volverá a llamar, de nuevo, independientemente de cuál sea el lado del and.

Este tipo particular de reescritura depende de la inmutabilidad o la estabilidad. Con where test_multi_calls1(30) != num, la reescritura de consultas ocurrirá para funciones inmutables pero no solo para funciones estables. Con where test_multi_calls1(num) != 30 no sucederá en absoluto (llamadas múltiples), aunque hay otras optimizaciones posibles:

Las expresiones que contienen solo funciones ESTABLE e IMMUTABLE se pueden usar con escaneos de índice. Las expresiones que contienen funciones VOLATILES no pueden. El número de llamadas puede o no disminuir, pero mucho más importante es que los resultados de las llamadas se utilizarán de una manera mucho más eficiente en el resto de la consulta (solo importa en tablas grandes, pero luego puede generar una gran cantidad de llamadas). diferencia).

En general, no piense en categorías de volatilidad en términos de memorización, sino en términos de dar oportunidades de PostgreSQL al planificador de consultas para reestructurar consultas completas de forma que sean lógicamente equivalentes (mismos resultados) pero mucho más eficientes.

+0

Cosas interesantes –

0

De acuerdo con documentation las funciones IMMUTABLE devolverán el mismo valor dados los mismos argumentos. Dado que está alimentando argumentos dinámicos (y ni siquiera el mismo una vez), el optimizador no tiene motivos para creer que obtendrá los mismos resultados y, por lo tanto, llama a la función. Mejor pregunta es: ¿por qué su consulta invoca la función varias veces si no es necesario?

+0

Incluso si codigo los argumentos, ej. 'SELECT test_multi_calls1 (10), test_multi_calls1 (10)' el mensaje se sigue imprimiendo dos veces. ¿Cuál es el punto del modificador 'INMUTABLE'? – EMP

+0

bueno, si esto sucede con argumentos codificados, probablemente deberías contactar a los desarrolladores de PostgreSQL. –

+1

¿Puedes pensar en un caso donde 'SELECT f (x), f (x)' tiene algún valor práctico que no sea demostrar que 'f (x)' se llama dos veces? El optimizador de consultas cuesta CPU y memoria, y tiene que ganarse su mantenimiento. Buscar casos como ese en cada consulta en caso de que estén allí degradaría cada consulta que se haya ejecutado, excepto esa. –

Cuestiones relacionadas