sql >> Base de Datos >  >> RDS >> Database

Columnas virtuales e índices funcionales

Con demasiada frecuencia, vemos consultas SQL complejas mal escritas que se ejecutan en las tablas de la base de datos. Dichas consultas pueden tardar muy poco o mucho tiempo en ejecutarse, pero consumen una gran cantidad de CPU y otros recursos. Sin embargo, en muchos casos, las consultas complejas brindan información valiosa a la aplicación/persona. Por lo tanto, trae activos útiles en todas las variedades de aplicaciones.

Complejidad de consultas

Miremos más de cerca las consultas problemáticas. Muchos de ellos son complejos. Eso puede deberse a varias razones:

  1. El tipo de datos elegido para los datos;
  2. La organización y almacenamiento de los datos en la base de datos;
  3. Transformación y unión de los datos en una consulta para recuperar el conjunto de resultados deseado.

Debe pensar estos tres factores clave correctamente e implementarlos correctamente para que las consultas funcionen de manera óptima.

Sin embargo, puede convertirse en una tarea casi imposible tanto para los desarrolladores de bases de datos como para los administradores de bases de datos. Por ejemplo, puede ser excepcionalmente difícil agregar nuevas funciones a los sistemas heredados existentes. Un caso particularmente complicado es cuando necesita extraer y transformar los datos de un sistema heredado para poder compararlos con los datos producidos por el nuevo sistema o funcionalidad. Debe lograrlo sin afectar la funcionalidad de la aplicación heredada.

Estas consultas pueden implicar uniones complejas, como las siguientes:

  1. Una combinación de subcadena y/o concatenación de varias columnas de datos;
  2. Funciones escalares integradas;
  3. UDF personalizados;
  4. Cualquier combinación de condiciones de búsqueda y comparaciones de cláusulas WHERE.

Las consultas, como se describió anteriormente, suelen tener rutas de acceso complejas. Lo que es peor, pueden tener muchos escaneos de tablas y/o escaneos de índice completo con tales combinaciones de JOIN o búsquedas.

Transformación y manipulación de datos en consultas

Necesitamos señalar que todos los datos almacenados de manera persistente en una tabla de base de datos necesitan transformación y/o manipulación en algún momento cuando consultamos esos datos de la tabla. La transformación puede ir desde una transformación simple hasta una muy compleja. Según lo compleja que sea, la transformación puede consumir una gran cantidad de CPU y recursos.

En la mayoría de los casos, las transformaciones realizadas en JOIN ocurren después de que los datos se leen y se descargan en tempdb. base de datos (SQL Server) o archivo de trabajo base de datos / espacios de tablas temporales como en otros sistemas de bases de datos.

Dado que los datos en el archivo de trabajo no son indexables , el tiempo necesario para ejecutar transformaciones combinadas y JOIN aumenta exponencialmente. Los datos recuperados aumentan de tamaño. Por lo tanto, las consultas resultantes se convierten en un cuello de botella de rendimiento debido al crecimiento adicional de datos.

Entonces, ¿cómo puede un desarrollador de bases de datos o un DBA resolver esos cuellos de botella de rendimiento rápidamente y también disponer de más tiempo para rediseñar y reescribir las consultas para un rendimiento óptimo?

Hay dos formas de resolver estos problemas persistentes de manera efectiva. Una de ellas es mediante el uso de columnas virtuales y/o índices funcionales.

Índices funcionales y consultas

Normalmente, crea índices en columnas que indican un conjunto único de columnas/valores en una fila (índices únicos o claves principales) o representan un conjunto de columnas/valores que se usan o pueden usarse en las condiciones de búsqueda de la cláusula WHERE de una consulta.

Si no tiene dichos índices y ha desarrollado consultas complejas como se describe anteriormente, notará lo siguiente:

  1. Reducción en los niveles de rendimiento cuando se usa explicar consultar y ver escaneos de tablas o escaneos de índice completo
  2. Uso de CPU y recursos muy alto causado por las consultas;
  3. Largos tiempos de ejecución.

Las bases de datos contemporáneas normalmente abordan estos problemas al permitirle crear un funcional o basado en funciones índice, como se nombra en SQLServer, Oracle y MySQL (v 8.x). O bien, puede ser Índice en expresión/basado en expresión índices, como en otras bases de datos (PostgreSQL y Db2).

Suponga que tiene una columna Fecha_de_compra del tipo de datos TIMESTAMP o DATETIME en su Pedido tabla, y esa columna ha sido indexada. Empezamos a consultar el Orden tabla con una cláusula WHERE:

SELECT ...
FROM Order
WHERE DATE(Purchase_Date) = '03.12.2020'

Esta transacción provocará el escaneo de todo el índice. Sin embargo, si la columna no ha sido indexada, obtendrá un escaneo de la tabla.

Después de escanear todo el índice, ese índice se mueve a tempdb/workfile (mesa completa si obtiene un escaneo de tabla ) antes de hacer coincidir el valor 03.12.2020 .

Como una tabla de pedidos grande utiliza una gran cantidad de CPU y recursos, debe crear un índice funcional que tenga la expresión DATE (Purchase_Date ) como una de las columnas de índice y se muestra a continuación:

CREATE ix_DatePurchased on sales.Order(Date(Purchase_Date) desc, ... )

Al hacerlo, crea el predicado coincidente FECHA (Fecha_de_compra) ='03.12.2020' indexable. Por lo tanto, en lugar de mover el índice o la tabla a tempdb/archivo de trabajo antes de la coincidencia del valor, hacemos que el índice solo se acceda y/o escanee parcialmente. Da como resultado un menor uso de CPU y recursos.

Echa un vistazo a otro ejemplo. Hay un Cliente tabla con las columnas first_name, last_name . Esas columnas están indexadas como tales:

CREATE INDEX ix_custname on Customer(first_name asc, last_name asc),

Además, tiene una vista que concatena estas columnas en nombre_cliente columna:

CREATE view v_CustomerInfo( customer_name, .... ) as
select first_name ||' '|| last_name as customer_name,.....
from Customer
where ...

Tiene una consulta de un sistema de comercio electrónico que busca el nombre completo del cliente:

select c.*
from v_CustomerInfo c
where c.customer_name = 'John Smith'
....

Nuevamente, esta consulta producirá un escaneo de índice completo. En el peor de los casos, será un escaneo completo de la tabla moviendo todos los datos del índice o la tabla al archivo de trabajo antes de la concatenación del first_name y apellido columnas y haciendo coincidir el valor 'John Smith'.

Otro caso es crear un índice funcional como se muestra a continuación:

CREATE ix_fullcustname on sales.Customer( first_name ||' '|| last_name desc, ... )

De esta forma, puede convertir la concatenación en la consulta de vista en un predicado indexable. En lugar de un escaneo de índice completo o un escaneo de tabla, tiene un escaneo de índice parcial. La ejecución de una consulta de este tipo da como resultado un menor uso de la CPU y los recursos, lo que excluye el trabajo en el archivo de trabajo y, por lo tanto, garantiza un tiempo de ejecución más rápido.

Columnas y consultas virtuales (generadas)

Las columnas generadas (columnas virtuales o columnas calculadas) son columnas que contienen los datos generados sobre la marcha. Los datos no se pueden establecer explícitamente en un valor específico. Hace referencia a los datos de otras columnas consultadas, insertadas o actualizadas en una consulta DML.

La generación de valores de tales columnas se automatiza en base a una expresión. Estas expresiones pueden generar:

  1. Una secuencia de valores enteros;
  2. El valor basado en los valores de otras columnas en la tabla;
  3. Puede generar valores llamando a funciones integradas o funciones definidas por el usuario (UDF).

Es igualmente importante tener en cuenta que en algunas bases de datos (SQLServer, Oracle, PostgreSQL, MySQL y MariaDB) estas columnas se pueden configurar para almacenar los datos de forma persistente con la ejecución de las instrucciones INSERT y UPDATE, o ejecutar la expresión de la columna subyacente sobre la marcha. si consultamos la tabla y la columna ahorrando espacio de almacenamiento.

Sin embargo, cuando la expresión es complicada, como ocurre con la lógica compleja en la función UDF, es posible que los ahorros en tiempo de ejecución, recursos y costos de consulta de CPU no sean tantos como se esperaba.

Por lo tanto, podemos configurar la columna para que almacene persistentemente el resultado de la expresión en una instrucción INSERT o UPDATE. Luego, creamos un índice regular en esa columna. De esta manera, ahorraremos la CPU, el uso de recursos y el tiempo de ejecución de la consulta. Nuevamente, podría ser un ligero aumento en el rendimiento de INSERTAR y ACTUALIZAR, según la complejidad de la expresión.

Veamos un ejemplo. Declaramos la tabla y creamos un índice de la siguiente manera:

CREATE TABLE Customer as (
  customerID Int GENERATED ALWAYS AS IDENTITY,
  first_name VARCHAR(50) NOT NULL,
  last_name VARCHAR(50) NOT NULL,
  customer_name as (first_name ||' '|| last_name) PERSISTED
  ...
  );
CREATE ix_fullcustname on sales.Customer( customer_name desc, ... )

De esta forma, movemos la lógica de concatenación de la vista del ejemplo anterior a la tabla y almacenamos los datos de forma persistente. Recuperamos los datos usando un escaneo coincidente en un índice regular. Es el mejor resultado posible aquí.

Al agregar una columna generada a una tabla y crear un índice regular en esa columna, podemos mover la lógica de transformación al nivel de la tabla. Aquí, almacenamos de forma persistente los datos transformados en declaraciones de inserción o actualización que, de lo contrario, se transformarían en consultas. Los escaneos JOIN e INDEX serán mucho más simples y rápidos.

Índices funcionales, columnas generadas y JSON

Las aplicaciones web y móviles globales utilizan estructuras de datos ligeras como JSON para mover los datos desde la web/dispositivo móvil a la base de datos y viceversa. El tamaño reducido de las estructuras de datos JSON hace que la transferencia de datos a través de la red sea rápida y sencilla. Es fácil comprimir JSON a un tamaño muy pequeño en comparación con otras estructuras, es decir, XML. Puede superar a las estructuras en el análisis de tiempo de ejecución.

Debido al mayor uso de estructuras de datos JSON, las bases de datos relacionales tienen el formato de almacenamiento JSON como tipo de datos BLOB o tipo de datos CLOB. Ambos tipos hacen que los datos de dichas columnas no sean indexables.

Por esta razón, los proveedores de bases de datos introdujeron funciones JSON para consultar y modificar objetos JSON, ya que puede integrar fácilmente estas funciones en la consulta SQL u otros comandos DML. Sin embargo, estas consultas dependen de la complejidad de los objetos JSON. Consumen mucho CPU y recursos, ya que los objetos BLOB y CLOB deben descargarse en la memoria o, peor aún, en el archivo de trabajo antes de la consulta y/o manipulación.

Supongamos que tenemos un Cliente tabla con los Detalles del cliente datos almacenados como un objeto JSON en una columna llamada CustomerDetail . Configuramos la consulta de la tabla de la siguiente manera:

SELECT CustomerID,
  JSON_VALUE(CustomerDetail, '$.customer.Name') AS Name,
  JSON_VALUE(CustomerDetail, '$.customer.Surname') AS Surname,
  JSON_VALUE(CustomerDetail, '$.customer.address.PostCode') AS PostCode,
  JSON_VALUE(CustomerDetail, '$.customer.address."Address Line 1"') + ' '
  + JSON_VALUE(CustomerDetail, '$.customer.address."Address Line 2"') AS Address,
  JSON_QUERY(CustomerDetail, '$.customer.address.Country') AS Country
FROM Customer
WHERE ISJSON(CustomerDetail) > 0
  AND JSON_VALUE(CustomerDetail, '$.customer.address.Country') = 'Iceland'
  AND JSON_VALUE(CustomerDetail, '$.customer.address.PostCode') IN (101,102,110,210,220)
  AND Status = 'Active'
ORDER BY JSON_VALUE(CustomerDetail, '$.customer.address.PostCode')

En este ejemplo, estamos consultando los datos de los clientes que viven en algunas partes de la región de la capital en Islandia. Todos Activos los datos deben recuperarse en el archivo de trabajo antes de aplicar el predicado de búsqueda. Aún así, la recuperación resultará en un uso de CPU y recursos demasiado grande.

En consecuencia, existe un procedimiento efectivo para hacer que las consultas JSON se ejecuten más rápido. Implica utilizar la funcionalidad a través de columnas generadas, como se describió anteriormente.

Logramos el aumento de rendimiento agregando columnas generadas. Una columna generada buscaría en el documento JSON datos específicos representados en la columna utilizando las funciones JSON y almacenaría el valor en la columna.

Podemos indexar y consultar estas columnas generadas usando SQL regular donde condiciones de búsqueda de cláusulas. Por lo tanto, la búsqueda de datos particulares en objetos JSON se vuelve muy rápida.

Agregamos dos columnas generadas:País y Código Postal :

ALTER TABLE Customer
ADD Country as JSON_VALUE(CustomerDetail,'$.customer.address.Country');
ALTER TABLE Customer
ADD PostCode as JSON_VALUE(CustomerDetail,'$.customer.address.PostCode');

CREATE INDEX ix_CountryPostCode on Country(Country asc,PostCode asc);

Además, creamos un índice compuesto en las columnas específicas. Ahora, podemos cambiar la consulta al ejemplo que se muestra a continuación:

SELECT CustomerID,
  JSON_VALUE(CustomerDetail, '$.customer.customer.Name') AS Name,
  JSON_VALUE(CustomerDetail, '$.customer.customer.Surname') AS Surname,
  JSON_VALUE(CustomerDetail, '$.customer.address.PostCode') AS PostCode,
  JSON_VALUE(CustomerDetail, '$.customer.address."Address Line 1"') + ' '
  + JSON_VALUE(CustomerDetail, '$.customer.address."Address Line 2"') AS Address,
  JSON_QUERY(CustomerDetail, '$.customer.address.Country') AS Country
FROM Customer
WHERE ISJSON(CustomerDetail) > 0
  AND Country = 'Iceland'
  AND PostCode IN (101,102,110,210,220)
  AND Status = 'Active'
ORDER BY JSON_VALUE(CustomerDetail, '$.customer.address.PostCode')

Esto limita la recuperación de datos a Clientes activos solo en alguna parte de la región de la capital de Islandia. Esta forma es más rápida y eficiente que la consulta anterior.

Conclusión

En general, al aplicar columnas virtuales o índices funcionales a las tablas que causan dificultades (CPU y consultas con muchos recursos), podemos eliminar los problemas con bastante rapidez.

Las columnas virtuales y los índices funcionales pueden ayudar a consultar objetos JSON complejos almacenados en tablas relacionales normales. Sin embargo, debemos evaluar los problemas cuidadosamente de antemano y hacer los cambios necesarios en consecuencia.

En algunos casos, si las estructuras de datos de consulta y/o JSON son muy complejas, una parte del uso de la CPU y los recursos puede pasar de las consultas a los procesos INSERTAR / ACTUALIZAR. Nos brinda menos ahorros generales de CPU y recursos de lo esperado. Si experimenta problemas similares, es posible que sea inevitable un rediseño más completo de tablas y consultas.