Son muchas las cosas que se hacen utilizando triggers que pueden hacerse
también utilizando el sistema de las reglas de
Postgres. Lo que actualmente no se puede
implementar a través de reglas son algunos tipos de restricciones
(constraints). Es posible situar una regla cualificada que reescriba una
query a NOTHING si el valor de la columna no aparece en otra tabla, pero
entonces los datos son eliminados silenciosamente, y eso no es una buena
idea. Si se necesitan comprobaciones para valores válidos, y en el caso de
aparecer un valor inválido dar un mensaje de error, eso deberá hacerse por
ahora con un trigger.
Por otro lado, un trigger que se dispare a partir de una INSERT en una
vista puede hacer lo mismo que una regla, situar los datos en cualquier
otro sitio y suprimir la inserción en una vista. Pero no puede hacer lo
mismo en una UPDATE o una DELETE, poruqe no hay datos reales en la relación
vista que puedan ser comprobados, y por ello el trigger nunca podría ser
llamado. Sólo una regla podría ayudarnos.
Para los tratamientos que podrían implementarse de ambas formas, dependerá
del uso de la base de datos cuál sea la mejor. Un trigger se dispara para
cada fila afectada. Una regla manipula el árbol de traducción o genera uno
adicional. De modo que si se manupulan muchas filas en una instrucción, una
regla ordenando una query adicional usualmente daría un mejor resultado que
un trigger que se llama para cada fila individual y deberá ejecutar sus
operaciones muchas veces.
Por ejemplo: hay dos tablas.
CREATE TABLE computer (
hostname text -- indexed
manufacturer text -- indexed
);
CREATE TABLE software (
software text, -- indexed
hostname text -- indexed
); |
Ambas tablas tienen muchos millares de filas y el índice sobre hostname es
único. La columna hostname contiene el nombre de dominio cualificado
completo del ordenador. La regla/trigger debería desencadenar el borrado de
filas de la tabla software que se refieran a un host borrado. Toda vez que
el trigger se llama para cada fila individual borrada de computer, se puede
usar la instrucción
DELETE FROM software WHERE hostname = $1; |
en un plan preparado y salvado, y pasar el hostname en el parámetro.
La regla debería ser escrita como
CREATE RULE computer_del AS ON DELETE TO computer
DO DELETE FROM software WHERE hostname = OLD.hostname; |
Veremos ahora en que se diferencian los dos tipos de delete. En el caso de
una
DELETE FROM computer WHERE hostname = 'mypc.local.net'; |
La tabla computer se revisa por índice (rápido) y la query lanzada por el
trigger también debería ser un barrido de índice (rápido también). La query
extra para la regla sería una
DELETE FROM software WHERE computer.hostname = 'mypc.local.net'
AND software.hostname = computer.hostname; |
Puesto que se han creado los índices apropiados, el optimizador creará un
plan de
Nestloop
-> Index Scan using comp_hostidx on computer
-> Index Scan using soft_hostidx on software |
De modo que no habría mucha diferencia de velocidad entre la implementación
del trigger y de la regla. Con la siguiente delete, queremos mostrar borrar
los 2000 ordenadores cuyo hostname empieza con 'old'. Hay dos posibles
queries para hacer eso. Una es
DELETE FROM computer WHERE hostname >= 'old'
AND hostname < 'ole' |
Donde el plan de ejecución para la query de la regla será
Hash Join
-> Seq Scan on software
-> Hash
-> Index Scan using comp_hostidx on computer |
La otra query posible es
DELETE FROM computer WHERE hostname ~ '^old'; |
con un plan de ejecución
Nestloop
-> Index Scan using comp_hostidx on computer
-> Index Scan using soft_hostidx on software |
Esto muestra que el optimizador no comprueba que la cualificación sobre
hostname en computer también debería se utilizado para un barrido por
índice en software donde hay múltiples expresiones de cualificación
combinadas con AND, que el hace en la versión regexp de la query. El
trigger será invocado una vez para cada una de los 2000 viejos ordenadores
que serán borrados, lo que dará como resultado un barrido por índice sobre
computer y 2000 barridos por índice sobre software. La implementación de la
regla lo hará con dos queries sobre índices. Y dependerá del tamaño
promedio de la tabla software si la regla será más rápida en una situación
de barrido secuencial. 2000 ejecuciones de queries sobre el gestor SPI
toman su tiempo, incluso si todos los bloques del índice se encuentran en
la memoría caché.
La última query que veremos es
DELETE FROM computer WHERE manufacurer = 'bim'; |
De nuevo esto debería dar como resultado muchoas filas para borrar de
computer. Por ello el trigger disparará de nuevo muchas queries sobre el
ejecutor. Pero el plan de las reglas será de nuevo un bucle anidado sobre
dos barridos de índice. Sólo usando otro índice en computer:
Nestloop
-> Index Scan using comp_manufidx on computer
-> Index Scan using soft_hostidx on software |
dando como resultado de la query de las reglas
DELETE FROM software WHERE computer.manufacurer = 'bim'
AND software.hostname = computer.hostname; |
En cualquiera de estos casos, las queries extra del sistema de reglas serán
más o menos independientes del número de filas afectadas en la query.
Otra situación son los casos de UPDATE donde depende del cambio de un
atributo si la acción debería realizarse o no. En la versión 6.4 de
Postgres, la especificación de atributos para
acontencimientos de reglas se ha deshabilitado (y tendrá su regreso en la
6.5, quizá antes ¡permanezcan en antena!). De modo que por ahora la única
forma de crear una regla como en el ejemplo de shoelace_log es hacerlo con
una cualficación de la regla. Eso da como resultado una query adicional que
se realiza siempre, incluso si el atributo que nos interesa no puede ser
cambiado de ninguna forma porque no aparece en la lista objetivo de la
query inicial. Cuando se habilite de nuevo, será una nueva ventaja del
sistema de reglas sobre los triggers. La optimización de un trigger deberá
fallar por definición en este caso, porque el hecjo de que su accoión solo
se hará cuando un atributo específico sea actualizado, está oculto a su
funcionalidad. La definición de un trigger sólo permite especificar el
nivel de fila, de modo que si se toca una fila, el trigger será llamado a
hacer su trabajo. El sistema de reglas lo sabrá mirándo la lista objetivo y
suprimirá la query adicional por completo si el atributo no se ha tocado.
De modo que la regla, cualificada o no, sólo hará sus barridos si tiene
algo que hacer.
Las reglas sólo serán significativamente más lentas que los triggers si sus
acciones dan como resultado joins grandes y mal cualificadas, una situación
en la que falla el optimizador. Tenemos un gran martillo. Utilizar un gran
martillo sin cuidado puede causar un gran daño, pero dar el toque correcto,
puede hundir cualquier clavo hasta la cabeza.