En un
artículo anterior
, describí el concepto y la implementación de una base de datos construida sobre la base de funciones, no tablas y campos como en las bases de datos relacionales. Dio muchos ejemplos que muestran las ventajas de este enfoque sobre el clásico. Muchos los encontraron no lo suficientemente convincentes.
En este artículo, mostraré cómo este concepto le permite equilibrar rápida y convenientemente la escritura y la lectura en la base de datos sin ningún cambio en la lógica del trabajo. Intentaron implementar una funcionalidad similar en DBMS comerciales modernos (en particular, Oracle y Microsoft SQL Server). Al final del artículo, mostraré lo que les sucedió, por decirlo suavemente, no muy.
Descripción
Como antes, para una mejor comprensión, comenzaré la descripción con ejemplos. Supongamos que necesitamos implementar una lógica que devuelva una lista de departamentos con el número de empleados en ellos y su salario total.
En una base de datos funcional, se verá así:
La complejidad de ejecutar esta consulta en cualquier DBMS será equivalente a
O (número de empleados) , ya que para este cálculo debe escanear toda la tabla de empleados y luego agruparlos por departamento. También habrá una pequeña adición (creemos que hay muchos más empleados que departamentos) dependiendo del plan elegido
O (número de registro de empleados) u
O (número de departamentos) para la agrupación, etc.
Está claro que la sobrecarga para la ejecución puede ser diferente en diferentes DBMS, pero la complejidad no cambiará de ninguna manera.
En la implementación propuesta, el DBMS funcional formará una subconsulta, que calculará los valores necesarios para el departamento y luego se unirá a la tabla del departamento para obtener el nombre. Sin embargo, para cada función, al declarar, es posible especificar un marcador MATERIALIZADO especial. El sistema creará automáticamente un campo apropiado para cada función. Cuando cambia el valor de una función, el valor del campo cambiará en la misma transacción. Al acceder a esta función, ya se realizará una apelación al campo calculado.
En particular, si configura MATERIALIZADO para las funciones
countEmployees y
salarrySum , en la tabla con la lista de departamentos se agregarán dos campos en los que se almacenará el número de empleados y su salario total. Con cualquier cambio en los empleados, sus salarios o afiliación con los departamentos, el sistema cambiará automáticamente los valores de estos campos. La consulta anterior comenzará a acceder a estos campos directamente y se ejecutará para
O (número de departamentos) .
¿Cuáles son las limitaciones? Solo una cosa: dicha función debe tener un número finito de valores de entrada para los que se define su valor. De lo contrario, será imposible construir una tabla que almacene todos sus valores, ya que no puede haber una tabla con un número infinito de filas.
Un ejemplo:
Esta función se define para un número infinito de valores del número N (por ejemplo, cualquier valor negativo es adecuado). Por lo tanto, no se puede poner MATERIALIZADO. Por lo tanto, esta es una limitación lógica y no técnica (es decir, no porque no hayamos podido implementar esto). De lo contrario, no hay restricciones. Puede usar la agrupación, la clasificación, AND y OR, PARTITION, recursión, etc.
Por ejemplo, en la tarea 2.2 del artículo anterior, puede poner MATERIALIZADO en ambas funciones:
El propio sistema creará una tabla con claves de los tipos
Cliente ,
Producto e
INTEGER , le agregará dos campos y actualizará los valores de los campos en ellos con cualquier cambio. Tras nuevas llamadas a estas funciones, no se calcularán, pero se leerán los valores de los campos correspondientes.
Con este mecanismo, puede, por ejemplo, deshacerse de la recursividad (CTE) en las consultas. En particular, considere los grupos que forman el árbol usando la relación hijo / padre (cada grupo tiene un enlace a su padre):
En una base de datos funcional, la lógica de recursión se puede definir de la siguiente manera:
Dado que MATERIALIZED está fijado para la función
isParent , se
creará una tabla con dos claves (grupos), en la que el campo
isParent será verdadero solo si la primera clave es descendiente de la segunda. El número de entradas en esta tabla será igual al número de grupos multiplicado por la profundidad promedio del árbol. Si es necesario, por ejemplo, calcular el número de descendientes de un determinado grupo, puede acceder a esta función:
No habrá CTE en la consulta SQL. En cambio, habrá un simple GROUP BY.
Con este mecanismo, también puede desnormalizar fácilmente la base de datos si es necesario:
Cuando llame a la función de
fecha para la línea de orden, la lectura será de la tabla con líneas de orden del campo para el que hay un índice. Al cambiar la fecha del pedido, el propio sistema recalculará automáticamente la fecha desnormalizada en la línea.
Los beneficios
¿Por qué se necesita todo este mecanismo? En los DBMS clásicos, sin reescribir consultas, un desarrollador o un DBA solo pueden cambiar índices, determinar estadísticas y decirle al planificador de consultas cómo ejecutarlos (además, las SUGERENCIAS están disponibles solo en DBMS comerciales). No importa cuánto lo intenten, no podrán cumplir la primera solicitud del artículo para
O (número de departamentos) sin cambiar las solicitudes y agregar desencadenantes. En el esquema propuesto, en la etapa de desarrollo, no tiene que pensar en la estructura del almacenamiento de datos y qué agregaciones usar. Todo esto se puede cambiar fácilmente sobre la marcha, directamente en funcionamiento.
En la práctica, esto es lo siguiente. Algunas personas desarrollan lógica directamente basada en la tarea. No están versados en algoritmos y su complejidad, ni en planes de ejecución, ni en tipos de join'ov, ni en ningún otro componente técnico. Estas personas son más analistas de negocios que desarrolladores. Luego, todo entra en pruebas u operación. El registro de consultas largas está habilitado. Cuando se detecta una solicitud larga, otras personas (más técnicas, de hecho DBA) deciden incluir MATERIALIZADO en alguna función intermedia. De este modo, la grabación se ralentiza un poco (ya que se requiere actualizar un campo adicional en una transacción). Sin embargo, no solo esta solicitud se acelera significativamente, sino también todas las demás que usan esta función. Al mismo tiempo, tomar una decisión sobre qué función particular materializar es relativamente simple. Dos parámetros principales: el número de posibles valores de entrada (exactamente cuántos registros habrá en la tabla correspondiente) y con qué frecuencia se usa en otras funciones.
Análogos
Los DBMS comerciales modernos tienen mecanismos similares: VISTA MATERIALIZADA con FAST REFRESH (Oracle) e INDEXED VIEW (Microsoft SQL Server). En PostgreSQL MATERIALIZED VIEW no se puede actualizar en una transacción, sino solo a pedido (e incluso con restricciones muy estrictas), por lo que no lo consideramos. Pero tienen varios problemas, lo que limita en gran medida su uso.
Primero, puede habilitar la materialización solo si ya ha creado una VISTA normal. De lo contrario, tendrá que volver a escribir las solicitudes restantes para acceder a la vista recién creada para poder utilizar esta materialización. O déjelo como está, pero será al menos ineficaz si hay ciertos datos ya calculados, pero muchas consultas no siempre los usan, sino que los calcula de nuevo.
En segundo lugar, tienen una gran cantidad de restricciones:
Oráculo5.3.8.4 Restricciones generales sobre la actualización rápida
La consulta de definición de la vista materializada está restringida de la siguiente manera:
- La vista materializada no debe contener referencias a expresiones no repetitivas como
SYSDATE
y ROWNUM
. - La vista materializada no debe contener referencias a tipos de datos
RAW
o LONG
RAW
. - No puede contener una subconsulta de lista
SELECT
. - No puede contener funciones analíticas (por ejemplo,
RANK
) en la cláusula SELECT
. - No puede hacer referencia a una tabla en la que se define un índice
XMLIndex
. - No puede contener una cláusula
MODEL
. - No puede contener una cláusula
HAVING
con una subconsulta. - No puede contener consultas anidadas que tengan
ANY
, ALL
o NOT
EXISTS
. - No puede contener una cláusula
[START WITH …] CONNECT BY
. - No puede contener varias tablas de detalles en diferentes sitios.
ON
COMMIT
las vistas materializadas no pueden tener tablas de detalles remotas.- Las vistas materializadas anidadas deben tener una unión o agregado.
- Las vistas de unión materializadas y las vistas agregadas materializadas con una cláusula
GROUP
BY
no se pueden seleccionar de una tabla organizada por índice.
5.3.8.5 Restricciones a la actualización rápida en vistas materializadas con combinaciones solamente
La definición de consultas para vistas materializadas solo con combinaciones y sin agregados tiene las siguientes restricciones para la actualización rápida:
- Todas las restricciones de " Restricciones generales sobre la actualización rápida ".
- No pueden tener cláusulas o agregados
GROUP
BY
. - Los Rowids de todas las tablas en la lista
FROM
deben aparecer en la lista SELECT
de la consulta. - Los registros de vista materializada deben existir con rowids para todas las tablas base en la lista
FROM
de la consulta. - No puede crear una vista materializada rápida y actualizable desde varias tablas con combinaciones simples que incluyen una columna de tipo de objeto en la instrucción
SELECT
.
Además, el método de actualización que elija no será óptimamente eficiente si:
- La consulta de definición utiliza una combinación externa que se comporta como una combinación interna. Si la consulta de definición contiene dicha unión, considere reescribir la consulta de definición para que contenga una unión interna.
- La lista
SELECT
de la vista materializada contiene expresiones en columnas de varias tablas.
5.3.8.6 Restricciones a la actualización rápida en vistas materializadas con agregados
La definición de consultas para vistas materializadas con agregados o combinaciones tiene las siguientes restricciones en la actualización rápida:
La actualización rápida es compatible con las vistas materializadas ON
COMMIT
y ON
DEMAND
, sin embargo, se aplican las siguientes restricciones:
- Todas las tablas en la vista materializada deben tener registros de vista materializados, y los registros de vista materializada deben:
- Contiene todas las columnas de la tabla referenciada en la vista materializada.
- Especifique con
ROWID
e INCLUDING
NEW
VALUES
. - Especifique la cláusula
SEQUENCE
si se espera que la tabla tenga una combinación de inserciones / cargas directas, eliminaciones y actualizaciones.
- Solo
SUM
, COUNT
, AVG
, VARIANCE
, VARIANCE
, MIN
y MAX
son compatibles para una actualización rápida. COUNT(*)
debe especificarse.- Las funciones agregadas deben aparecer solo como la parte más externa de la expresión. Es decir, los agregados como
AVG(AVG(x))
o AVG(x)
+ AVG(x)
no están permitidos. - Para cada agregado como
AVG(expr)
, el COUNT(expr)
correspondiente COUNT(expr)
debe estar presente. Oracle recomienda que se especifique SUM(expr)
. - Si se especifica
STDDEV(expr
VARIANCE(expr)
o STDDEV(expr
), COUNT(expr)
y SUM(expr)
deben especificarse. Oracle recomienda que se especifique SUM(expr *expr)
. - La columna
SELECT
en la consulta de definición no puede ser una expresión compleja con columnas de varias tablas base. Una posible solución a esto es usar una vista materializada anidada. - La lista
SELECT
debe contener todas las columnas GROUP
BY
. - La vista materializada no se basa en una o más tablas remotas.
- Si usa un tipo de datos
CHAR
en las columnas de filtro de un registro de vista materializada, los juegos de caracteres del sitio maestro y la vista materializada deben ser los mismos. - Si la vista materializada tiene uno de los siguientes, la actualización rápida solo es compatible con inserciones DML convencionales y cargas directas.
- Vistas materializadas con agregados
MIN
o MAX
- Vistas materializadas que tienen
SUM(expr)
pero no COUNT(expr)
- Vistas materializadas sin
COUNT(*)
Dicha vista materializada se denomina vista materializada de solo inserción. - Una vista materializada con
MAX
o MIN
se puede actualizar rápidamente después de eliminar o mezclar sentencias DML si no tiene una cláusula WHERE
.
La actualización rápida máxima / mínima después de eliminar o mezclar DML no tiene el mismo comportamiento que el caso de solo inserción. Elimina y vuelve a calcular los valores máximos / mínimos para los grupos afectados. Debe ser consciente de su impacto en el rendimiento. - Las vistas materializadas con vistas con nombre o subconsultas en la cláusula
FROM
se pueden actualizar rápidamente siempre que las vistas se puedan combinar por completo. Para obtener información sobre qué vistas se combinarán, consulte la Referencia del lenguaje SQL de la base de datos Oracle . - Si no hay combinaciones externas, puede tener selecciones arbitrarias y combinaciones en la cláusula
WHERE
. - Las vistas agregadas materializadas con uniones externas se pueden actualizar rápidamente después de DML convencional y cargas directas, siempre que solo se haya modificado la tabla externa. Además, deben existir restricciones únicas en las columnas de unión de la tabla de unión interna. Si hay uniones externas, todas las uniones deben estar conectadas por
AND
sy deben usar el operador de igualdad ( =
). - Para las vistas materializadas con
CUBE
, ROLLUP
, conjuntos de agrupación o concatenación de ellas, se aplican las siguientes restricciones:
- La lista
SELECT
debe contener un distintivo de agrupación que puede ser una función GROUPING_ID
en todas las expresiones GROUP
BY
o funciones GROUPING
una para cada expresión GROUP
BY
. Por ejemplo, si la cláusula GROUP
BY
de la vista materializada es " GROUP
BY
CUBE(a, b)
", la lista SELECT
debe contener " GROUPING_ID(a, b)
" o " GROUPING(a)
AND
GROUPING(b)
"para que la vista materializada se actualice rápidamente. GROUP
BY
no debe dar lugar a agrupaciones duplicadas. Por ejemplo, " GROUP BY a, ROLLUP(a, b)
" no se puede actualizar rápidamente porque da como resultado agrupaciones duplicadas " (a), (a, b), AND (a)
".
5.3.8.7 Restricciones a la actualización rápida en vistas materializadas con UNION ALL
Las vistas materializadas con el operador conjunto UNION
ALL
admiten la opción REFRESH
FAST
si se cumplen las siguientes condiciones:
- La consulta de definición debe tener el operador
UNION
ALL
en el nivel superior.
El operador UNION
ALL
no se puede incrustar dentro de una subconsulta, con una excepción: UNION
ALL
puede estar en una subconsulta en la cláusula FROM
siempre que la consulta de definición tenga la forma SELECT * FROM
(vista o subconsulta con UNION
ALL
) como en el siguiente ejemplo:
CREAR VISTA view_with_unionall AS
(SELECCIONE c.rowid crid, c.cust_id, 2 umarker
DE clientes c DONDE c.cust_last_name = 'Smith'
UNIÓN TODO
SELECCIONE c.rowid crid, c.cust_id, 3 umarker
DE clientes c DONDE c.cust_last_name = 'Jones');
CREAR VISTA MATERIALIZADA unionall_inside_view_mv
ACTUALIZACIÓN RÁPIDA EN LA DEMANDA
SELECCIONAR * DESDE view_with_unionall;
Tenga en cuenta que la vista view_with_unionall
cumple los requisitos para una actualización rápida. - Cada bloque de consulta en la consulta
UNION
ALL
debe cumplir los requisitos de una vista materializada rápida y actualizable con agregados o una vista materializada rápida y renovable con uniones.
Los registros de vista materializada apropiados se deben crear en las tablas según sea necesario para el tipo correspondiente de vista materializada rápida y actualizable.
Tenga en cuenta que la base de datos Oracle también permite el caso especial de una vista materializada de una sola tabla con combinaciones solo si la columna ROWID
se ha incluido en la lista SELECT
y en el registro de vista materializada. Esto se muestra en la consulta de definición de la vista view_with_unionall
. - La lista
SELECT
de cada consulta debe incluir un marcador UNION
ALL
, y la columna UNION
ALL
debe tener un valor numérico o de cadena constante diferente en cada rama UNION
ALL
. Además, la columna de marcador debe aparecer en la misma posición ordinal en la lista SELECT
de cada bloque de consulta. Consulte " Marcador de UNION ALL y reescritura de consultas " para obtener más información sobre los marcadores UNION
ALL
. - Algunas funciones, como las uniones externas, las consultas de vistas materializadas agregadas de solo inserción y las tablas remotas no son compatibles con las vistas materializadas con
UNION
ALL
. Sin embargo, tenga en cuenta que las vistas materializadas utilizadas en la replicación, que no contienen uniones o agregados, pueden actualizarse rápidamente cuando se utilizan UNION
ALL
o tablas remotas. - El parámetro de inicialización de compatibilidad debe establecerse en 9.2.0 o superior para crear una vista materializada rápida y actualizable con
UNION
ALL
.
No quiero ofender a los fanáticos de Oracle, pero a juzgar por su lista de limitaciones, parece que este mecanismo no se escribió en el caso general utilizando algún tipo de modelo, sino miles de indios, donde a todos se les permitió escribir su propio hilo, y cada uno de ellos pudo y lo hizo Usar este mecanismo para una lógica real es como caminar en un campo minado. En cualquier momento, puede obtener una mina, golpeando una de las limitaciones no obvias. Cómo funciona esto también es un tema aparte, pero está fuera del alcance de este artículo.
Microsoft SQL ServerRequerimientos Adicionales
Además de las opciones SET y los requisitos de la función determinista, se deben cumplir los siguientes requisitos:
- El usuario que ejecuta
CREATE INDEX
debe ser el propietario de la vista. - Cuando crea el índice, la opción
IGNORE_DUP_KEY
debe establecerse en OFF (la configuración predeterminada). - Las tablas deben estar referenciadas por nombres de dos partes, esquema . nombre de tabla en la definición de vista.
- Las funciones definidas por el usuario a las que se hace referencia en la vista deben crearse utilizando la opción
WITH SCHEMABINDING
. - Cualquier función definida por el usuario a la que se haga referencia en la vista debe estar referenciada por nombres de dos partes, <schema> . <función> .
- La propiedad de acceso a datos de una función definida por el usuario debe ser
NO SQL
, y la propiedad de acceso externo debe ser NO
. - Las funciones de Common Language Runtime (CLR) pueden aparecer en la lista de selección de la vista, pero no pueden ser parte de la definición de la clave de índice agrupada. Las funciones CLR no pueden aparecer en la cláusula WHERE de la vista o en la cláusula ON de una operación JOIN en la vista.
- Las funciones y métodos CLR de los tipos definidos por el usuario CLR utilizados en la definición de vista deben tener las propiedades establecidas como se muestra en la siguiente tabla.
- La vista debe crearse utilizando la opción
WITH SCHEMABINDING
. - La vista debe hacer referencia solo a las tablas base que están en la misma base de datos que la vista. La vista no puede hacer referencia a otras vistas.
- La instrucción SELECT en la definición de vista no debe contener los siguientes elementos de Transact-SQL:
1 La vista indizada puede contener columnas flotantes ; sin embargo, tales columnas no se pueden incluir en la clave de índice agrupado. - Si
GROUP BY
está presente, la definición VIEW debe contener COUNT_BIG(*)
y no debe contener HAVING
. Estas restricciones de GROUP BY
son aplicables solo a la definición de vista indizada. Una consulta puede usar una vista indizada en su plan de ejecución incluso si no cumple con estas restricciones GROUP BY
. - Si la definición de vista contiene una cláusula
GROUP BY
, la clave del índice agrupado único puede hacer referencia solo a las columnas especificadas en la cláusula GROUP BY
.
Aquí puede ver que los indios no se sintieron atraídos, ya que decidieron hacerlo de acuerdo con el esquema "haremos poco, pero bien". Es decir, tienen más minas en el campo, pero su ubicación es más transparente. Lo más angustiante es esta limitación:
La vista debe hacer referencia solo a las tablas base que están en la misma base de datos que la vista. La vista no puede hacer referencia a otras vistas.
En nuestra terminología, esto significa que una función no puede acceder a otra función materializada. Corta toda la ideología de raíz.
Además, esta limitación (y más adelante en el texto) reduce en gran medida los casos de uso:
La instrucción SELECT en la definición de vista no debe contener los siguientes elementos de Transact-SQL:
OUTER JOINS, UNION, ORDER BY y otros están prohibidos. Quizás fue más fácil indicar qué se puede usar que qué no. La lista probablemente sería mucho más pequeña.
Para resumir: un gran conjunto de restricciones en cada DBMS (noto comercial) versus ninguno (con la excepción de uno lógico en lugar de técnico) en la tecnología LGPL. Sin embargo, debe tenerse en cuenta que implementar este mecanismo en la lógica relacional es algo más complicado que en el funcional descrito.
Implementación
Como funciona PostgreSQL se utiliza como una "máquina virtual". En el interior hay un algoritmo complejo que genera consultas. Aquí está el
código fuente . Y no solo hay un gran conjunto de heurísticas con un montón de ifs. Entonces, si tiene un par de meses para estudiar, puede intentar comprender la arquitectura.
¿Funciona de manera eficiente? Efectivamente suficiente. Desafortunadamente, probar esto es difícil. Solo puedo decir que si considera las miles de solicitudes que se encuentran en aplicaciones grandes, en promedio son más efectivas que un buen desarrollador. Un excelente programador de SQL puede escribir cualquier consulta de manera más eficiente, pero con mil consultas, simplemente no tendrá la motivación ni el tiempo para hacerlo. Lo único que puedo dar ahora como evidencia de efectividad es que, sobre la base de la plataforma construida en este DBMS, varios proyectos del
sistema ERP funcionan en los que hay miles de funciones MATERIALIZADAS diferentes, con miles de usuarios y bases de datos de terrabyte con cientos de millones de registros funcionando en un servidor de doble procesador normal. Sin embargo, cualquiera puede probar / refutar la efectividad descargando la
plataforma y PostgreSQL,
permitiendo el registro de consultas SQL e intentando cambiar la lógica y los datos allí.
En los siguientes artículos, también hablaré sobre cómo puede colgar restricciones en las funciones, trabajar con sesiones de cambio y mucho más.