Com muita frequência, vemos consultas SQL complexas mal escritas sendo executadas nas tabelas do banco de dados. Essas consultas podem levar um tempo muito curto ou muito longo para serem executadas, mas consomem uma enorme quantidade de CPU e outros recursos. No entanto, em muitos casos, consultas complexas fornecem informações valiosas para o aplicativo/pessoa. Portanto, traz ativos úteis em todas as variedades de aplicações.
Complexidade das consultas
Vejamos mais de perto as consultas problemáticas. Muitos deles são complexos. Isso pode ser devido a vários motivos:
- O tipo de dados escolhido para os dados;
- A organização e armazenamento dos dados no banco de dados;
- Transformação e junção dos dados em uma consulta para recuperar o conjunto de resultados desejado.
Você precisa pensar nesses três fatores principais corretamente e implementá-los corretamente para que as consultas tenham um desempenho ideal.
No entanto, pode se tornar uma tarefa quase impossível para desenvolvedores de banco de dados e DBAs. Por exemplo, pode ser excepcionalmente difícil adicionar novas funcionalidades aos sistemas Legacy existentes. Um caso particularmente complicado é quando você precisa extrair e transformar os dados de um sistema legado para poder compará-los com os dados produzidos pelo novo sistema ou funcionalidade. Você precisa alcançá-lo sem afetar a funcionalidade do aplicativo legado.
Essas consultas podem envolver junções complexas, como as seguintes:
- Uma combinação de substring e/ou concatenação de várias colunas de dados;
- Funções escalares incorporadas;
- UDFs personalizados;
- Qualquer combinação de comparações da cláusula WHERE e condições de pesquisa.
As consultas, conforme descrito anteriormente, geralmente têm caminhos de acesso complexos. O que é pior, eles podem ter muitas varreduras de tabela e/ou varreduras de índice completo com essas combinações de JOINs ou pesquisas ocorrendo.
Transformação de dados e manipulações em consultas
Precisamos ressaltar que todos os dados armazenados persistentemente em uma tabela de banco de dados precisam de transformação e/ou manipulação em algum momento quando consultamos esses dados da tabela. A transformação pode variar de uma transformação simples a uma muito complexa. Dependendo da complexidade, a transformação pode consumir muita CPU e recursos.
Na maioria dos casos, as transformações feitas em JOINs acontecem depois que os dados são lidos e transferidos para o tempdb banco de dados (SQL Server) ou arquivo de trabalho banco de dados / espaços de tabela temporários como em outros sistemas de banco de dados.
Como os dados no arquivo de trabalho não são indexáveis , o tempo necessário para executar transformações combinadas e JOINs aumenta exponencialmente. Os dados recuperados tornam-se maiores. Assim, as consultas resultantes se transformam em um gargalo de desempenho por meio do crescimento adicional de dados.
Então, como um desenvolvedor de banco de dados ou um DBA pode resolver esses gargalos de desempenho rapidamente e também ter mais tempo para reprojetar e reescrever as consultas para obter o desempenho ideal?
Existem duas maneiras de resolver esses problemas persistentes de forma eficaz. Uma delas é usar colunas virtuais e/ou índices funcionais.
Índices e consultas funcionais
Normalmente, você cria índices em colunas que indicam um conjunto exclusivo de colunas/valores em uma linha (índices exclusivos ou chaves primárias) ou representam um conjunto de colunas/valores que são ou podem ser usados nas condições de pesquisa da cláusula WHERE de uma consulta.
Se você não tiver esses índices e tiver desenvolvido consultas complexas conforme descrito anteriormente, observará o seguinte:
- Redução nos níveis de desempenho ao usar o explicar consultar e ver varreduras de tabela ou varreduras de índice completo
- Uso de recursos e CPU muito alto causado pelas consultas;
- Longos tempos de execução.
Os bancos de dados contemporâneos normalmente abordam esses problemas permitindo que você crie um arquivo funcional ou baseado em função index, conforme nomeado no SQLServer, Oracle e MySQL (v 8.x). Ou pode ser Indexar em expressão/baseada em expressão índices, como em outros bancos de dados (PostgreSQL e Db2).
Suponha que você tenha uma coluna Purchase_Date do tipo de dados TIMESTAMP ou DATETIME em seu Pedido tabela e essa coluna foi indexada. Começamos a consultar o Pedido tabela com uma cláusula WHERE:
SELECT ...
FROM Order
WHERE DATE(Purchase_Date) = '03.12.2020'
Esta transação causará a varredura de todo o índice. No entanto, se a coluna não tiver sido indexada, você obterá uma verificação de tabela.
Depois de verificar todo o índice, esse índice se move para tempdb / workfile (tabela inteira se você receber uma varredura de tabela ) antes de corresponder ao valor 03.12.2020 .
Como uma grande tabela Order usa muita CPU e recursos, você deve criar um índice funcional com a expressão DATE (Purchase_Date ) como uma das colunas de índice e mostrado abaixo:
CREATE ix_DatePurchased on sales.Order(Date(Purchase_Date) desc, ... )
Ao fazer isso, você cria o predicado correspondente DATE (Purchase_Date) ='03.12.2020' indexável. Assim, ao invés de mover o índice ou tabela para o tempdb/workfile antes da correspondência do valor, fazemos com que o índice seja acessado e/ou escaneado apenas parcialmente. Isso resulta em menor uso de CPU e recursos.
Dê uma olhada em outro exemplo. Existe um Cliente tabela com as colunas first_name, last_name . Essas colunas são indexadas como tal:
CREATE INDEX ix_custname on Customer(first_name asc, last_name asc),
Além disso, você tem uma visualização que concatena essas colunas no customer_name coluna:
CREATE view v_CustomerInfo( customer_name, .... ) as
select first_name ||' '|| last_name as customer_name,.....
from Customer
where ...
Você tem uma consulta de um sistema de comércio eletrônico que pesquisa o nome completo do cliente:
select c.*
from v_CustomerInfo c
where c.customer_name = 'John Smith'
....
Novamente, essa consulta produzirá uma verificação completa do índice. Na pior das hipóteses, será uma verificação completa da tabela movendo todos os dados do índice ou da tabela para o arquivo de trabalho antes da concatenação do first_name e last_name colunas e correspondendo ao valor 'John Smith'.
Outro caso é criar um índice funcional conforme mostrado abaixo:
CREATE ix_fullcustname on sales.Customer( first_name ||' '|| last_name desc, ... )
Dessa forma, você pode fazer a concatenação na consulta de exibição em um predicado indexável. Em vez de uma varredura de índice completa ou varredura de tabela, você tem uma varredura de índice parcial. Essa execução de consulta resulta em menor uso de CPU e recursos, excluindo o trabalho no arquivo de trabalho e, assim, garantindo um tempo de execução mais rápido.
Colunas e consultas virtuais (geradas)
Colunas geradas (colunas virtuais ou colunas computadas) são colunas que armazenam os dados gerados em tempo real. Os dados não podem ser definidos explicitamente para um valor específico. Refere-se aos dados em outras colunas consultadas, inseridas ou atualizadas em uma consulta DML.
A geração de valores dessas colunas é automatizada com base em uma expressão. Essas expressões podem gerar:
- Uma sequência de valores inteiros;
- O valor baseado nos valores de outras colunas na tabela;
- Pode gerar valores chamando funções internas ou funções definidas pelo usuário (UDFs).
É igualmente importante observar que em alguns bancos de dados (SQLServer, Oracle, PostgreSQL, MySQL e MariaDB) essas colunas podem ser configuradas para armazenar persistentemente os dados com a execução de instruções INSERT e UPDATE ou executar a expressão de coluna subjacente em tempo real se consultarmos a tabela e a coluna economizando espaço de armazenamento.
No entanto, quando a expressão é complicada, como acontece com lógica complexa na função UDF, a economia de tempo de execução, recursos e custos de consulta de CPU pode não ser tanto quanto o esperado.
Assim, podemos configurar a coluna para que ela armazene persistentemente o resultado da expressão em uma instrução INSERT ou UPDATE. Em seguida, criamos um índice regular nessa coluna. Dessa forma, economizaremos a CPU, o uso de recursos e o tempo de execução da consulta. Novamente, pode haver um pequeno aumento no desempenho de INSERT e UPDATE, dependendo da complexidade da expressão.
Vejamos um exemplo. Declaramos a tabela e criamos um índice da seguinte forma:
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, ... )
Dessa forma, movemos a lógica de concatenação da exibição no exemplo anterior para a tabela e armazenamos os dados de forma persistente. Recuperamos os dados usando uma varredura correspondente em um índice regular. É o melhor resultado possível aqui.
Ao adicionar uma coluna gerada a uma tabela e criar um índice regular nessa coluna, podemos mover a lógica de transformação para o nível da tabela. Aqui, armazenamos persistentemente os dados transformados em instruções de inserção ou atualização que, de outra forma, seriam transformadas em consultas. As varreduras de JOIN e INDEX serão muito mais simples e rápidas.
Índices funcionais, colunas geradas e JSON
Aplicativos móveis e da Web globais usam estruturas de dados leves, como JSON, para mover os dados da Web/dispositivo móvel para o banco de dados e vice-versa. O tamanho reduzido das estruturas de dados JSON torna a transferência de dados pela rede rápida e fácil. É fácil compactar JSON para um tamanho muito pequeno em comparação com outras estruturas, ou seja, XML. Ele pode superar as estruturas na análise de tempo de execução.
Devido ao aumento do uso de estruturas de dados JSON, os bancos de dados relacionais têm o formato de armazenamento JSON como tipo de dados BLOB ou tipo de dados CLOB. Ambos os tipos tornam os dados em tais colunas não indexáveis como estão.
Por esse motivo, os fornecedores de banco de dados introduziram funções JSON para consultar e modificar objetos JSON, pois você pode integrar facilmente essas funções na consulta SQL ou em outros comandos DML. No entanto, essas consultas dependem da complexidade dos objetos JSON. Eles consomem muito CPU e recursos, pois os objetos BLOB e CLOB precisam ser descarregados na memória ou, pior, no arquivo de trabalho antes de consultar e/ou manipular.
Suponha que temos um Cliente tabela com os Detalhes do cliente dados armazenados como um objeto JSON em uma coluna chamada CustomerDetail . Configuramos a consulta da tabela conforme abaixo:
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')
Neste exemplo, estamos consultando os dados de clientes que moram em algumas partes da região da capital na Islândia. Todos Ativos os dados devem ser recuperados no arquivo de trabalho antes de aplicar o predicado de pesquisa. Ainda assim, a recuperação resultará em uso muito grande de CPU e recursos.
Assim, existe um procedimento eficaz para tornar as consultas JSON mais rápidas. Envolve a utilização da funcionalidade por meio de colunas geradas, conforme descrito anteriormente.
Alcançamos o aumento de desempenho adicionando colunas geradas. Uma coluna gerada pesquisaria no documento JSON por dados específicos representados na coluna usando as funções JSON e armazenaria o valor na coluna.
Podemos indexar e consultar essas colunas geradas usando condições regulares de pesquisa da cláusula where do SQL. Portanto, pesquisar dados específicos em objetos JSON se torna muito rápido.
Adicionamos duas colunas geradas – País e 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);
Além disso, criamos um índice composto nas colunas específicas. Agora, podemos alterar a consulta para o exemplo exibido abaixo:
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')
Isso limita a recuperação de dados a Clientes Ativos apenas em alguma parte da Região da Capital da Islândia. Desta forma é mais rápido e eficiente que a consulta anterior.
Conclusão
Em suma, aplicando colunas virtuais ou índices funcionais a tabelas que causam dificuldades (CPU e consultas com muitos recursos), podemos eliminar problemas rapidamente.
Colunas virtuais e índices funcionais podem ajudar na consulta de objetos JSON complexos armazenados em tabelas relacionais regulares. No entanto, precisamos avaliar os problemas cuidadosamente com antecedência e fazer as mudanças necessárias de acordo.
Em alguns casos, se as estruturas de dados de consulta e/ou JSON forem muito complexas, uma parte do uso da CPU e dos recursos pode mudar das consultas para os processos INSERT/UPDATE. Isso nos dá menos economia geral de CPU e recursos do que o esperado. Se você tiver problemas semelhantes, o redesenho de tabelas e consultas mais completo pode ser inevitável.