web-dev-qa-db-fra.com

Comment les valeurs NULL affectent-elles les performances dans une recherche de base de données?

Dans notre produit, nous avons un moteur de recherche générique et nous essayons d'optimiser les performances de recherche. Un grand nombre des tables utilisées dans les requêtes autorisent des valeurs NULL. Devrions-nous repenser notre table pour interdire les valeurs nulles pour l'optimisation ou non?

Notre produit fonctionne à la fois sur Oracle et MS SQL Server.

28

Dans Oracle, les valeurs NULL ne sont pas indexées, i. e. cette requête:

SELECT  *
FROM    table
WHERE   column IS NULL

utilisera toujours une analyse complète de la table, car index ne couvre pas les valeurs dont vous avez besoin.

Plus que cela, cette requête:

SELECT  column
FROM    table
ORDER BY
        column

utilisera également le balayage complet de la table et le tri pour la même raison.

Si vos valeurs n'autorisent pas intrinsèquement les NULL, marquez la colonne avec NOT NULL.

26
Quassnoi

Une réponse supplémentaire pour attirer l'attention sur le commentaire de David Aldridge sur la réponse acceptée de Quassnoi.

La déclaration:

cette requête:

SELECT * FROM table WHERE colonne IS NULL

utilisera toujours l'analyse complète de la table

ce n'est pas vrai. Voici l'exemple de compteur utilisant un index avec une valeur littérale:

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.

Comme vous pouvez le constater, l’index est utilisé.

Cordialement, .__ Rob.

13
Rob van Wijk

Réponse courte: oui, conditionnellement!

Le problème principal en ce qui concerne les valeurs nulles et les performances concerne les recherches directes.

Si vous insérez une ligne dans une table, avec des valeurs NULL, elle est placée dans la page naturelle à laquelle elle appartient. Toute requête recherchant cet enregistrement le trouvera à l'endroit approprié. Facile jusqu'ici ....

... mais disons que la page se remplit et que cette rangée est blottie parmi les autres rangées. Ça va toujours bien ...

... jusqu'à ce que la ligne soit mise à jour et que la valeur null contienne maintenant quelque chose. La taille de la ligne a augmenté au-delà de l'espace disponible. Le moteur de base de données doit donc faire quelque chose. 

Le moyen le plus rapide pour le serveur est de déplacer la ligne off cette page dans une autre et de remplacer l’entrée de la ligne par un pointeur direct. Malheureusement, cela nécessite une recherche supplémentaire lorsqu'une requête est effectuée: une recherche de l'emplacement naturel de la ligne et une autre pour trouver son emplacement actuel.

Donc, la réponse courte à votre question est oui, rendre ces champs non nullables aidera les performances de recherche. Cela est particulièrement vrai s'il arrive souvent que les champs nuls dans les enregistrements sur lesquels vous effectuez une recherche soient mis à jour pour devenir non nuls.

Bien sûr, il existe d’autres pénalités (notamment les E/S, bien que dans une moindre mesure la profondeur de l’index) associées à des jeux de données plus volumineux, puis vous rencontrez des problèmes d’application avec le refus des valeurs nulles dans les champs qui les exigent sur le plan conceptuel, mais c’est un autre problème :)

8
Jeremy Smyth

Si votre colonne ne contient pas de valeurs NULL, il est préférable de déclarer cette colonne NOT NULL, l'optimiseur pourra peut-être utiliser un chemin plus efficace.

Toutefois, si vous avez des valeurs NULL dans votre colonne, vous n’avez pas beaucoup de choix (une valeur par défaut non NULL peut créer plus de problèmes qu’elle ne résout).

Comme mentionné par Quassnoi, les valeurs NULL ne sont pas indexées dans Oracle ou, pour être plus précis, une ligne ne sera pas indexée si toutes les colonnes indexées sont NULL, cela signifie:

  • que les valeurs NULL peuvent potentiellement accélérer votre recherche car l'index aura moins de lignes
  • vous pouvez toujours indexer les lignes NULL si vous ajoutez une autre colonne NOT NULL à l'index ou même une constante.

Le script suivant illustre un moyen d'indexer les valeurs NULL:

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;
5
Vincent Malgrat

Je dirais que les tests sont nécessaires, mais il est agréable de connaître les expériences des autres peuples. D'après mon expérience sur le serveur ms SQL, les valeurs NULL peuvent causer des problèmes de performances (différences) considérables. Au cours d’un test très simple, j’ai vu une requête revenir en 45 secondes lorsque non définie sur null dans les champs connexes de la commande create table et plus de 25 minutes où elle n’était pas définie (j’ai abandonné l’attente et pris un pic à le plan de requête estimé).

Les données de test correspondent à 1 million de lignes x 20 colonnes construites à partir de 62 caractères alphabétiques minuscules aléatoires sur un disque dur normal i5-3320 et de 8 Go RAM (SQL Server utilisant 2 Go)/SQL Server 2012 Enterprise Edition sous Windows 8.1. Il est important d'utiliser des données aléatoires/irrégulières pour que le test devienne un cas "pire" réaliste. Dans les deux cas, la table a été recréée et rechargée avec des données aléatoires d'environ 30 secondes sur des fichiers de base de données disposant déjà d'une quantité d'espace libre suffisante.

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,

pour des raisons de performances, les deux avaient l'option de table data_compression = ensemble de pages et tout le reste était défini par défaut. Aucun index.

alter table myTable rebuild partition = all with (data_compression = page);

Ne pas avoir de valeurs nulles est une condition requise pour les tables optimisées en mémoire pour lesquelles je n'utilise pas spécifiquement. Cependant, le serveur SQL fera évidemment ce qui est le plus rapide. table crée.

Toutes les requêtes ultérieures de la même forme sur cette table reviennent dans deux secondes, je suppose donc que les statistiques par défaut standard et que la table (1,3 Go) soit correctement insérée dans la mémoire fonctionnent bien .. c'est-à-dire.

select count(field19) from myTable where field19 
                       not in (select field18 from myTable) 1000000

En revanche, le fait de ne pas avoir de null et de ne pas avoir à traiter des cas nuls rend les requêtes beaucoup plus simples, plus courtes, moins sujettes aux erreurs et très normalement plus rapidement. Dans la mesure du possible, il vaut mieux éviter les valeurs NULL généralement sur le serveur MS SQL au moins si elles ne sont pas explicitement requises et ne peuvent raisonnablement pas être résolues à partir de la solution.

Commencer avec une nouvelle table et redimensionner celle-ci jusqu'à 10 m lignes/13 Go. La même requête prend 12 minutes, ce qui est très respectable compte tenu du matériel et de l'absence d'index en cours d'utilisation. Pour info, la requête était complètement liée à IO avec IO oscillant entre 20 Mo/s et 60 Mo/s. Une répétition de la même requête a pris 9 minutes.

4
Andrew

La question de savoir s'il faut utiliser les valeurs Null car elles affectent les performances est l'un de ces problèmes d'équilibrage de la conception de la base de données. Vous devez trouver un équilibre entre les besoins de l'entreprise et les performances. 

Les null doivent être utilisés s'ils sont nécessaires. Par exemple, vous pouvez avoir une date de début et une date de fin dans un tableau. Souvent, vous ne connaissez pas la date de fin au moment de la création de l'enregistrement. Par conséquent, vous devez autoriser les valeurs NULL, qu'elles affectent les performances ou non, car les données ne sont tout simplement pas stockées. Toutefois, si les données doivent, selon les règles de gestion, exister au moment de la création de l'enregistrement, vous ne devez pas autoriser les données. Nuls. Cela améliorerait les performances, simplifierait un peu le codage et garantirait la préservation de l'intégrité des données. 

Si vous avez des données existantes que vous souhaitez modifier pour ne plus autoriser les valeurs NULL, vous devez alors prendre en compte l'impact de cette modification. Tout d’abord, savez-vous quelle valeur vous devez attribuer aux enregistrements qui sont actuellement nuls? Deuxièmement, avez-vous beaucoup de code qui utilise isnull ou coalesce que vous devez mettre à jour (ces choses ralentissent les performances, donc si vous n'avez plus besoin de les vérifier, vous devriez changer le code)? Avez-vous besoin d'une valeur par défaut? Pouvez-vous vraiment en assigner un? Si ce n'est pas le cas, une partie du code d'insertion ou de mise à jour sera rompue si elle ne considère pas que le champ ne peut plus être nul. Parfois, les gens mettent de mauvaises informations pour leur permettre de se débarrasser des valeurs nulles. Alors maintenant, le champ de prix doit contenir des valeurs décimales et des choses comme «inconnu» et ne peut donc pas être correctement un type de données décimal. Vous devez ensuite utiliser toutes sortes de longueurs pour pouvoir effectuer des calculs. Cela crée souvent des problèmes de performances aussi mauvais ou pires que le null créé. En outre, vous devez parcourir tout votre code et lorsque vous utilisez une référence indiquant que le fichier est nul ou non, vous devez réécrire pour exclure ou inclure en fonction des éventuelles mauvaises valeurs que quelqu'un mettra parce que les données ne sont pas autorisées. être nul. 

J'effectue un grand nombre d'importations de données à partir de données client et chaque fois que nous obtenons un fichier dans lequel un champ ne devrait pas autoriser les valeurs NULL, nous obtenons des données erronées qui doivent être nettoyées avant l'importation dans notre système. L'email est l'un d'entre eux. Souvent, les données sont entrées sans connaître cette valeur et il s’agit généralement d’un type de données de chaîne, de sorte que l’utilisateur peut taper n'importe quoi ici. Nous allons importer des emails et trouver des choses "je ne sais pas". Difficile d'essayer d'envoyer un email à "je ne sais pas". Si le système exige une adresse électronique valide et vérifie l'existence ou non d'un signe @, nous obtiendrons '[email protected] ". En quoi des données erronées de ce type sont-elles utiles aux utilisateurs des données? 

Certains problèmes de performances liés aux valeurs NULL résultent de l'écriture de requêtes non argumentables. Parfois, le simple fait de réorganiser la clause where plutôt que d'éliminer un null nécessaire peut améliorer les performances.

3
HLGEM

Les champs nullables peuvent avoir un impact important sur les performances lors de l'exécution de requêtes "NOT IN". Etant donné que les lignes contenant tous les champs indexés définis sur null ne sont pas indexées dans un index B-Tree, Oracle doit effectuer une analyse complète de la table pour rechercher les valeurs null, même lorsqu'un index existe.

Par exemple:

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 |
---------------------------------------------------------------------------

La requête doit vérifier les valeurs NULL et doit donc effectuer une analyse complète de la table t2 pour chaque ligne de t1.

Maintenant, si nous rendons les champs non nullables, il peut utiliser l'index.

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 |
-----------------------------------------------------------------------------
3
Daniel Emge

D'après mon expérience, NULL est une valeur valide et signifie généralement "ne sait pas". Si vous ne le savez pas, il est vraiment inutile de créer une valeur par défaut pour la colonne ou d'essayer d'appliquer une contrainte NOT NULL. NULL se trouve être un cas spécifique.

Le vrai défi pour les NULL est que cela complique un peu la récupération. Par exemple, vous ne pouvez pas dire WHERE nom_colonne IN (NULL, 'valeur1', 'valeur2').

Personnellement, si vous trouvez beaucoup de vos colonnes, ou que certaines colonnes contiennent beaucoup de NULL, je pense que vous voudrez peut-être revoir votre modèle de données. Peut-être que ces colonnes nulles peuvent être placées dans une table enfant? Par exemple: une table avec des numéros de téléphone dont le nom, le numéro de téléphone, le numéro de téléphone, le numéro de téléphone, le numéro de téléphone, le numéro de téléphone, le numéro de téléphone, le numéro de téléphone, etc.

Ce que vous devez faire est de revenir en arrière et voir comment les données seront accessibles. Est-ce une colonne qui devrait avoir une valeur? Est-ce une colonne qui n'a de valeur que dans certains cas? Est-ce une colonne qui sera beaucoup interrogée?

0
David