2010-02-22 18 views
0

J'ai un problème avec une requête générée par Hibernate qui n'utilise pas d'index. L'accès à la base de données est fait à partir de Java en utilisant JTDS et la version du serveur est SQL Server , dernier service pack. Le champ est nullable et est une clé étrangère qui, dans certains scénarios spécifiques, peut être complètement nulle, la colonne est indexée via un index non cluster mais l'index n'est jamais utilisé lorsque la colonne est entièrement nulle, créant un grand nombre des analyses de table complètes et des problèmes de performance.Problème lors de l'utilisation d'index avec SQL Server 2005 et Hibernate

La situation pourrait être vérifiée aussi en utilisant l'analyseur de requête standard avec le code SQL suivant:

Créer une table et des index

CREATE TABLE [dbo].[TestNulls](
    [PK] [varchar](36) NOT NULL, 
    [DATA] [varchar](36) NULL, 
    [DATANULL] [varchar](36) NULL, 
CONSTRAINT [PK_TestNulls] PRIMARY KEY NONCLUSTERED 
(
[PK] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,  
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 

CREATE NONCLUSTERED INDEX [IDX_DATA] ON [dbo].[TestNulls] 
(
[DATA] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, 
IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, 
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
GO 

CREATE NONCLUSTERED INDEX [IDX_DATANULL] ON [dbo].[TestNulls] 
(
[DATANULL] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, 
IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON,  
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
GO 

Remplissez-le avec des données aléatoires en utilisant la fonction newid

declare @i as int 
set @i = 0 
while (@i < 500000) 
begin 
    set nocount on 

    insert into TestNulls values(NEWID(), NEWID(), null) 
    insert into TestNulls values(NEWID(), null, null) 
    insert into TestNulls values(NEWID(), null, null)   
    set @i = (@i + 1) 
    set nocount on 
end; 

Cette requête effectue un balayage de table complet

declare @p varchar(36) 
set @p = NEWID() 
select PK, DATA, DATANULL from TestNulls 
where DATANULL = @p 

Si je complète la requête avec un "et DATANULL IS NOT NULL" la requête utilise maintenant l'index.

Besoin d'aide:

  • Comment puis-je forcer la combinaison JTDS/Hibernate pour utiliser l'index (le sendStringParametersAsUnicode est déjà définie sur false par défaut)?
  • Existe-t-il un moyen d'ajouter "et column is not null" pour toutes les requêtes hibernate qui utilisent un champ Nullable?
  • Une explication de ce problème?

Cordialement Massimo

Répondre

1

1) Je pense, nous devons éviter les valeurs NULL. Utilisez simplement DEFAULT et placez {00000-0000-000 ...} comme valeur NULL. Votre script de remplissage de données génère trop de valeurs NULL, donc la sélectivité des valeurs de ce champ est très faible. Je pense que SQL Server choisira de scanner puis utilisera l'index dans ce cas (SQL Server choisit automatiquement d'utiliser ou n'utilise pas l'index lui-même). Et ça fait le sens. Vous devriez analyser vos données REAL. De toute façon vous pouvez le forcer à utiliser simplement un index. Vous pouvez créer une procédure stockée sur le serveur sql puis l'interroger depuis hibernate, par exemple, ou commander hibernate pour utiliser une requête personnalisée pour demander des données (je pense que c'est possible) et ajouter un indicateur de table à votre requête pour forcer l'utilisation d'un index:

INDEX (index_val [, ... n]):

select PK, DATA, DATANULL from TestNulls WITH INDEX(IDX_DATANULL) 

la sélectivité est le "nombre de lignes"/"cardinalité", donc si vous avez 10K clients et rechercher toutes les "femelle", il faut considérer que la recherche retournerait 10K/2 = 5K lignes, donc une très mauvaise sélectivité.

Chance.

+0

Problème dans votre approche est que nous utilisons Hibernete pour générer des requêtes et donc nous ne voulons pas utiliser SP. En plus de cela SQL2008 n'a pas ce problème et l'ajout n'est pas nul le résout, donc nous cherchons un moyen simple de forcer la partie "n'est pas nulle" dans la requête sql de hibernate – massimogentilini

+0

Qu'en est-il: Critères critères = session .createCriteria (TestNulls.class); criteria.add (Expression.isNotNull ("DATANULL")); – garik

0

Vous utilisez une table sans index cluster ("table de segments", comme on l'appelle), qui n'est généralement pas très efficace pour les SELECT, car toute requête significative nécessite une recherche de signet ou une analyse de table complète. Ainsi, pour utiliser l'index, le serveur devra: 1) trouver les valeurs données dans l'index et récupérer les identifiants de ligne correspondants, 2) récupérer les lignes par les ID et renvoyer les données.

Compte tenu de la nature de vos données, l'optimiseur "pense" que l'analyse complète est plus efficace.

Je vous suggère d'essayer:

  1. Reconstruire les statistiques sur la table. Les statistiques obsolètes pourraient conduire l'optimiseur à de mauvaises décisions.
  2. Force l'utilisation de l'index via un indicateur. N'oubliez pas de tester s'il est vraiment plus rapide sur vos données réelles (il arrive parfois que l'optimiseur sache mieux que vous).
  3. Créer un index de couverture pour cette requête en ajoutant des données (il les insertions/mises à jour un peu plus lent, de sorte que vous devriez considérer l'impact global sur le système):

    CREATE INDEX IDX_DATANULL_FULL SUR TestNulls (DATANULL) COMPRENDRE (PK, DATA)