Muchas cosas que se pueden hacer usando disparadores (triggers) también se pueden implementar usando el sistema de reglas de PostgreSQL. Una de las cosas que no se pueden implementar mediante reglas son algunos tipos de restricciones, especialmente las claves foráneas. Es posible colocar una regla condicional que reescriba un comando a NOTHING si el valor de una columna no aparece en otra tabla. Pero entonces los datos se descartan silenciosamente y esa no es una buena idea. Si se requieren comprobaciones de valores válidos, y en el caso de un valor no válido se debe generar un mensaje de error, esto debe hacerse mediante un disparador (trigger).
En este capítulo, nos enfocamos en el uso de reglas para actualizar vistas. Todos los ejemplos de reglas de actualización en este capítulo también se pueden implementar usando disparadores INSTEAD OF en las vistas. Escribir tales disparadores suele ser más fácil que escribir reglas, particularmente si se requiere una lógica compleja para realizar la actualización.
Para las cosas que se pueden implementar con ambos, cuál es mejor depende del uso de la base de datos. Un disparador (trigger) se dispara una vez por cada fila afectada. Una regla modifica la consulta o genera una consulta adicional. Por lo tanto, si se ven afectadas muchas filas en una sola sentencia, es probable que una regla que emita un comando adicional sea más rápida que un disparador que se llama por cada fila individual y debe volver a determinar qué hacer muchas veces. Sin embargo, el enfoque del disparador es conceptualmente mucho más simple que el de la regla, y es más fácil de implementar correctamente para los principiantes.
Aquí mostramos un ejemplo de cómo se comporta la elección de reglas frente a disparadores (triggers) en una situación concreta. Hay dos tablas:
CREATE TABLE computer (
hostname text, -- indexed
manufacturer text -- indexed
);
CREATE TABLE software (
software text, -- indexed
hostname text -- indexed
);
Ambas tablas tienen miles de filas y los índices en hostname son únicos. La regla o el disparador (trigger) debe implementar una restricción que elimine las filas de software que hacen referencia a un ordenador de la tabla computer que haya sido eliminado. El disparador usaría este comando:
DELETE FROM software WHERE hostname = $1;
Dado que el disparador se llama para cada fila individual eliminada de computer, puede preparar y guardar el plan para este comando y pasar el valor de hostname en el parámetro. La regla se escribiría como:
CREATE RULE computer_del AS ON DELETE TO computer
DO DELETE FROM software WHERE hostname = OLD.hostname;
Ahora examinamos diferentes tipos de eliminaciones. En el caso de un:
DELETE FROM computer WHERE hostname = 'mypc.local.net';
la tabla computer se escanea por índice (rápido), y el comando emitido por el disparador también usaría un escaneo de índice (también rápido). El comando adicional de la regla sería:
DELETE FROM software WHERE computer.hostname = 'mypc.local.net'
AND software.hostname = computer.hostname;
Dado que hay índices apropiados configurados, el planificador creará un plan de:
Nestloop -> Index Scan using comp_hostidx on computer -> Index Scan using soft_hostidx on software
Por lo tanto, no habría tanta diferencia de velocidad entre la implementación del disparador y la de la regla.
Con la siguiente eliminación queremos deshacernos de todos los 2000 ordenadores donde el hostname comienza con old. Hay dos comandos posibles para hacer eso. Uno es:
DELETE FROM computer WHERE hostname >= 'old'
AND hostname < 'ole'
El comando añadido por la regla será:
DELETE FROM software WHERE computer.hostname >= 'old' AND computer.hostname < 'ole'
AND software.hostname = computer.hostname;
con el plan:
Hash Join
-> Seq Scan on software
-> Hash
-> Index Scan using comp_hostidx on computer
El otro comando posible es:
DELETE FROM computer WHERE hostname ~ '^old';
lo que da como resultado el siguiente plan de ejecución para el comando añadido por la regla:
Nestloop -> Index Scan using comp_hostidx on computer -> Index Scan using soft_hostidx on software
Esto muestra que el planificador no se da cuenta de que la condición para hostname en computer también podría usarse para un escaneo de índice en software cuando hay múltiples expresiones de condición combinadas con AND, que es lo que hace en la versión de expresión regular del comando. El disparador se invocará una vez por cada uno de los 2000 ordenadores antiguos que deben eliminarse, y eso dará como resultado un escaneo de índice sobre computer y 2000 escaneos de índice sobre software. La implementación de la regla lo hará con dos comandos que usan índices. Y depende del tamaño total de la tabla software si la regla seguirá siendo más rápida en la situación de escaneo secuencial. Las 2000 ejecuciones de comandos del disparador a través del administrador SPI toman algún tiempo, incluso si todos los bloques de índice pronto estarán en la caché.
El último comando que examinamos es:
DELETE FROM computer WHERE manufacturer = 'bim';
Nuevamente, esto podría resultar en muchas filas a eliminar de computer. Así que el disparador volverá a ejecutar muchos comandos a través del ejecutor. El comando generado por la regla será:
DELETE FROM software WHERE computer.manufacturer = 'bim'
AND software.hostname = computer.hostname;
El plan para ese comando será nuevamente el bucle anidado (nested loop) sobre dos escaneos de índices, solo que usando un índice diferente en computer:
Nestloop -> Index Scan using comp_manufidx on computer -> Index Scan using soft_hostidx on software
En cualquiera de estos casos, los comandos adicionales del sistema de reglas serán más o menos independientes del número de filas afectadas en un comando.
En resumen, las reglas solo serán significativamente más lentas que los disparadores (triggers) si sus acciones resultan en uniones grandes y mal condicionadas, una situación en la que el planificador falla.