Como valores NULL afetam o desempenho em uma pesquisa de banco de dados?
-
06-07-2019 - |
Pergunta
Em nosso produto temos um motor de busca genérico, e tentando optimze o desempenho da pesquisa. Muitas das tabelas usadas nas consultas permitir valores nulos. Devemos redesenhar nossa mesa para valores nulos rejeitar para otimização ou não?
nosso produto é executado em ambos Oracle
e MS SQL Server
.
Solução
Em Oracle
, valores NULL
não são indexados, i. e. esta consulta:
SELECT *
FROM table
WHERE column IS NULL
sempre usará varredura completa da tabela como o índice não cobre os valores que você precisa.
Mais do que isso, esta consulta:
SELECT column
FROM table
ORDER BY
column
também vai usar varredura completa da tabela e classificar para a mesma razão.
Se os seus valores não permitem intrinsecamente NULL
de, em seguida, marcar a coluna como NOT NULL
.
Outras dicas
Uma resposta extra para desenhar alguma atenção extra ao comentário de David Aldridge em resposta aceita de Quassnoi.
A declaração:
esta consulta:
SELECT * FROM tabela WHERE column IS NULL
sempre usará mesa cheia de digitalização
Não é verdade. Aqui está o exemplo contador usando um índice com um valor literal:
SQL> create table mytable (mycolumn)
2 as
3 select nullif(level,10000)
4 from dual
5 connect by level <= 10000
6 /
Table created.
SQL> create index i1 on mytable(mycolumn,1)
2 /
Index created.
SQL> exec dbms_stats.gather_table_stats(user,'mytable',cascade=>true)
PL/SQL procedure successfully completed.
SQL> set serveroutput off
SQL> select /*+ gather_plan_statistics */ *
2 from mytable
3 where mycolumn is null
4 /
MYCOLUMN
----------
1 row selected.
SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'))
2 /
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------
SQL_ID daxdqjwaww1gr, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ * from mytable where mycolumn
is null
Plan hash value: 1816312439
-----------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 2 |
|* 1 | INDEX RANGE SCAN| I1 | 1 | 1 | 1 |00:00:00.01 | 2 |
-----------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("MYCOLUMN" IS NULL)
19 rows selected.
Como você pode ver, o índice está sendo usado.
Saudações, Rob.
A resposta curta: sim, condicionalmente
O principal problema com valores nulos e desempenho tem a ver com as pesquisas diretas.
Se você inserir uma linha em uma tabela, com valores nulos, ele é colocado na página natural que ele pertence. Qualquer consulta olhando para esse registro vai encontrá-lo no local apropriado. Fácil até agora ....
... Mas vamos dizer que a página enche, e agora essa linha é abraçada por entre as outras linhas. Ainda indo bem ...
... até que a linha é atualizada, eo valor nulo agora contém alguma coisa. O tamanho da fila tem aumentado além do espaço disponível para isso, então o motor DB tem que fazer algo sobre isso.
A coisa mais rápida para o servidor a fazer é mover a linha off essa página para outra, e para substituir a entrada da linha com um ponteiro para a frente. Infelizmente, isso requer uma pesquisa extra quando uma consulta é realizada:. A encontrar a localização natural da linha, e um para encontrar a sua localização actual
Assim, a curta resposta à sua pergunta é sim, fazer aqueles campos não-anulável vai ajudar o desempenho da pesquisa. Isto é especialmente verdadeiro se muitas vezes acontece que os campos que você pesquisar na nulos em registros são atualizados para não nulo.
É claro, existem outras penalidades (nomeadamente I / O, embora a uma profundidade pequena índice de extensão) associado com conjuntos de dados maiores, e então você tem problemas de aplicativos com disallowing nulos em campos que conceitualmente exigem-los, mas hey, isso é outra problema:)
Se a sua coluna não contém NULLs é melhor declarar este NOT NULL
coluna, o otimizador pode ser capaz de tomar o caminho mais eficiente.
No entanto, se você tem nulos em sua coluna de você não tem muita escolha (um valor padrão não nulo pode criar mais problemas do que resolve).
Como Quassnoi mencionada, nulos não são indexados no Oracle, ou para ser mais preciso, uma linha não será indexado se todas as colunas indexadas são NULL, isto significa:
- que nulos podem potencialmente acelerar a sua investigação porque o índice terá menos linhas
- você ainda pode indexar os registros NULL se você adicionar outra coluna NOT NULL ao índice ou mesmo uma constante.
O script a seguir demonstra uma maneira de valores NULL de índice:
CREATE TABLE TEST AS
SELECT CASE
WHEN MOD(ROWNUM, 100) != 0 THEN
object_id
ELSE
NULL
END object_id
FROM all_objects;
CREATE INDEX idx_null ON test(object_id, 1);
SET AUTOTRACE ON EXPLAIN
SELECT COUNT(*) FROM TEST WHERE object_id IS NULL;
Eu diria que o teste é necessário, mas é bom saber que outras experiências dos povos. Na minha experiência no servidor MS SQL, nulos podem e causam problemas enormes de desempenho (diferenças). Em um teste muito simples agora vi um retorno de consulta em 45 segundos quando não nula foi definido nos campos relacionados na tabela a instrução CREATE e mais de 25 minutos, onde ele não foi definido (eu desisti de espera e só teve um pico em o plano de consulta estimado).
Os dados de teste é de 1 milhão de linhas x 20 colunas que são construídos a partir de 62 caracteres em minúsculas alfa aleatórios em um i5-3320 HD normal e 8GB de RAM (SQL Server usando 2GB) / SQL Server 2012 Enterprise Edition no Windows 8.1. É importante usar / dados irregulares dados aleatórios para fazer o teste de um caso realista "pior". Em ambos os casos mesa foi recriado e recarregado com dados aleatórios que levou cerca de 30 segundos em arquivos de banco de dados que já tinha uma quantidade adequada de espaço livre.
select count(field0) from myTable where field0
not in (select field1 from myTable) 1000000
CREATE TABLE [dbo].[myTable]([Field0] [nvarchar](64) , ...
vs
CREATE TABLE [dbo].[myTable]([Field0] [nvarchar](64) not null,
por razões de desempenho ambos tinham opção de tabela DATA_COMPRESSION = conjunto de páginas e tudo o mais foi cumprido. Nenhum índice.
alter table myTable rebuild partition = all with (data_compression = page);
Não ter nulos é um requisito para em tabelas de memória otimizado para que eu não estou usando especificamente servidor no entanto sql irá, obviamente, fazer o que é mais rápido que neste específico caso parece ser maciçamente em favor de não ter nulos nos dados e não usar nulo sobre a mesa criar.
Todas as consultas subsequentes da mesma forma neste retorno mesa em dois segundos, então eu assumiria estatísticas padrão padrão e, possivelmente, com o (1.3GB) Tabela de ajuste na memória estão funcionando bem. i.
select count(field19) from myTable where field19
not in (select field18 from myTable) 1000000
Em um aparte que não têm valores nulos e não ter que lidar com casos nulos também faz consultas muito mais simples, mais curto, menos propenso a erros e muito normalmente mais rápido. Se possível, melhor evitar nulos geralmente no servidor MS SQL, pelo menos, a menos que sejam explicitamente necessário e não pode razoavelmente ser trabalhado para fora da solução.
Começando com uma nova tabela e dimensionamento este até linhas 10m / 13GB mesma consulta leva 12 minutos, que é muito respeitável considerando o hardware e nenhum índice em uso. Para informações consulta foi completamente IO preso com IO pairando entre 20MB / s para 60MB / s. A repetição da mesma consulta levou 9 minutos.
campos anuláveis ??pode ter um grande impacto no desempenho ao fazer "NOT IN" consultas. Porque linhas com todos os campos indexados definido como nulo não são indexados em um índice B-Tree, a Oracle deve fazer uma varredura completa da tabela para verificar se há entires nulos, mesmo quando existe um índice.
Por exemplo:
create table t1 as select rownum rn from all_objects;
create table t2 as select rownum rn from all_objects;
create unique index t1_idx on t1(rn);
create unique index t2_idx on t2(rn);
delete from t2 where rn = 3;
explain plan for
select *
from t1
where rn not in ( select rn
from t2 );
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 50173 | 636K| 3162 (1)| 00:00:38 |
|* 1 | FILTER | | | | | |
| 2 | TABLE ACCESS FULL| T1 | 50205 | 637K| 24 (5)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| T2 | 45404 | 576K| 2 (0)| 00:00:01 |
---------------------------------------------------------------------------
A consulta tem de verificar os valores nulos por isso tem que fazer uma varredura completa da tabela de t2 para cada linha de T1.
Agora, se fizermos os campos não anulável, ele pode usar o índice.
alter table t1 modify rn not null;
alter table t2 modify rn not null;
explain plan for
select *
from t1
where rn not in ( select rn
from t2 );
-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2412 | 62712 | 24 (9)| 00:00:01 |
| 1 | NESTED LOOPS ANTI | | 2412 | 62712 | 24 (9)| 00:00:01 |
| 2 | INDEX FULL SCAN | T1_IDX | 50205 | 637K| 21 (0)| 00:00:01 |
|* 3 | INDEX UNIQUE SCAN| T2_IDX | 45498 | 577K| 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------
A questão de se usar nulos porque eles afetam o desempenho é um daqueles equilíbrio age de design de banco de dados. Você tem que equilibrar as necessidades de negócios contra o desempenho.
nulos deve ser usado se eles são necessários. Por exemplo, você pode ter uma data e uma data final em uma tabela começar. Muitas vezes você não saberia a data final no momento do registro é criado. Portanto, você deve permitir nulos se eles afetam o desempenho ou não que os dados simplesmente não está lá para ser colocado em. No entanto, se o mosto de dados, pelas regras de negócio, estar lá no momento do registro é criado, então você não deve permitir nulos. Este desempenho iria melhorar, tornar a codificação um pouco mais simples e certifique-se a integridade dos dados é preservada.
Se você tiver dados existentes que você gostaria de mudar para não permitir nulos, então você tem que considerar o impacto dessa mudança. Primeiro, você sabe o valor que você precisa colocar nos registros que são actualmente nulo? Em segundo lugar, você tem um monte de código que está usando isnull
ou coalesce
que você precisa de atualização (essas coisas desempenho lento, por isso, se você já não necessidade de verificá-los, você deve alterar o código)? Você precisa de um valor padrão? você pode realmente atribuir um? Se não vai algumas das pausa de inserção ou atualização de código se ele não considera que o campo não pode ser nulo. Às vezes as pessoas vão colocar em má informação que lhes permita se livrar de nulos. Então agora as necessidades de campo preço para conter valores decimais e coisas como 'desconhecido' e, portanto, não pode ser adequadamente um tipo de dados decimal e, em seguida, você tem que ir a todos os tipos de comprimentos, a fim de fazer cálculos. Isso muitas vezes cria problemas de desempenho tão ruim ou pior do que o nulo criado. Além disso, você precisa passar por todo o seu código e onde quer que você usou um refernce ao ser arquivado nulo ou não sendo nula, é necessário reescrever para excluir ou incluir baseado na possível que alguém valores ruim vai colocar em becasue os dados não é permitido para ser nulo.
Eu faço um monte de importações de dados a partir de dados do cliente e cada vez que obtemos um arquivo onde algum campo que deve permitir nulos não, obtemos dados de lixo que precisa ser limpos antes de importar para o nosso sistema. E-mail é uma delas. Muitas vezes, os dados são de entrada não saber este valor e é geralmente algum tipo de dados de cadeia, de modo que o usuário pode digitar nada aqui. Vamos para importar e-mails e encontrar coisas "Eu não sei". Difícil de tentar realmente enviar um e-mail com "Eu não sei". Se o sistema requres um endereço de email válido e verifica se há algo como a existência de um sinal @, obteríamos 'I@dont.know" Como é dados de lixo como este útil para os usuários dos dados?
Alguns dos problemas de desempenho com valores nulos são o resultado de escrever consultas nonsargable. Às vezes, apenas rearranjando a cláusula onde ao invés de eliminar um nulo necessário pode melhorar o desempenho.
Em minha experiência NULL é um valor válido e, geralmente, significa "não sei". Se você não sabe, então ele realmente é inútil para compensar algum valor padrão para a coluna ou para tentar impor alguma restrição NOT NULL. NULL só acontece de ser um caso específico.
O verdadeiro desafio para NULLs é complicar um pouco a recuperação. Por exemplo, você não pode dizer de onde column_name IN (NULL, 'value1', 'valor2').
Pessoalmente, se você encontrar muitas de suas colunas, ou determinadas colunas contêm uma grande quantidade de nulos eu acho que você pode querer rever seu modelo de dados. Talvez essas colunas nulos podem ser colocados em uma tabela filho? Por exemplo:. Uma tabela com números de telefone onde seu nome, homephone, celular, faxno, worknumber, emergencynumber etc ... Você pode apenas um preenchimento ou dois deles e seria melhor normalizar-lo
O que você precisa fazer é passo para trás e ver como os dados serão acessados. É este uma coluna que deve ter um valor? É este uma coluna que tem apenas um valor para certos casos? É este uma coluna que será consultado muito?