2011-08-12 1 views
1

tableau optin_channel_1 (pour chaque 'canal' il y a une table dédiée)Optimisation INDEX CLUSTERED pour une utilisation avec JOIN

CREATE TABLE [dbo].[optin_channel_1](
    [key_id] [bigint] NOT NULL, 
    [valid_to] [datetime] NOT NULL, 
    [valid_from] [datetime] NOT NULL, 
    [key_type_id] [int] NOT NULL, 
    [optin_flag] [tinyint] NOT NULL, 
    [source_proc_id] [int] NOT NULL, 
    [date_inserted] [datetime] NOT NULL 
) ON [PRIMARY] 

CREATE CLUSTERED INDEX [ix_id] ON [dbo].[optin_channel_1] 
(
    [key_type_id] ASC, 
    [key_id] ASC, 
    [valid_to] ASC, 
    [valid_from] 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] 

tableau profile_conns

CREATE TABLE [dbo].[profile_conns](
    [profile_key_id] [bigint] NOT NULL, 
    [valid_to] [datetime] NOT NULL, 
    [valid_from] [datetime] NOT NULL, 
    [conn_key_id] [bigint] NOT NULL, 
    [conn_key_type_id] [int] NOT NULL, 
    [conn_type_id] [int] NOT NULL, 
    [source_proc_id] [int] NOT NULL, 
    [date_inserted] [datetime] NOT NULL 
) ON [PRIMARY] 

CREATE CLUSTERED INDEX [ix_id] ON [dbo].[profile_conns] 
(
    [profile_key_id] ASC, 
    [conn_key_type_id] ASC, 
    [conn_key_id] ASC, 
    [valid_to] ASC, 
    [valid_from] 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] 

tableau lu_channel_conns

CREATE TABLE [dbo].[lu_channel_conns](
    [channel_id] [int] NOT NULL, 
    [conn_type_id] [int] NOT NULL, 
CONSTRAINT [PK_lu_channel_conns] PRIMARY KEY CLUSTERED 
(
    [channel_id] ASC, 
    [conn_type_id] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 

de table lu_conn_type

CREATE TABLE [dbo].[lu_conn_type](
    [conn_type_id] [int] NOT NULL, 
    [default_key_type_id] [int] NOT NULL, 
    [master_key_type_id] [int] NOT NULL, 
    [date_inserted] [datetime] NOT NULL, 
CONSTRAINT [PK_lu_conns] PRIMARY KEY CLUSTERED 
(
    [conn_type_id] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 

vue v_source_proc_id_by_group_id

SELECT DISTINCT x.source_proc_id, x.source_proc_group_id 
FROM lu_source_proc x INNER JOIN lu_source_proc_group y ON x.source_proc_group_id = y.group_id 

Il y a une instruction SQL dynamique va être exécuté:

SET @sql_str='SELECT @ret=MAX(o.optin_flag) 
    FROM optin_channel_'+CAST(@channel_id AS NVARCHAR(100))+' o 
    INNER HASH JOIN dbo.v_source_proc_id_by_group_id y ON o.source_proc_id=y.source_proc_id AND [email protected]_proc_group_id 
    INNER HASH JOIN profile_conns z ON z.profile_key_id=cast(@profile_key_id AS NVARCHAR(100)) AND z.conn_key_type_id=o.key_type_id AND z.conn_key_id=o.[key_id] AND z.valid_to=''01.01.3000'' 
    INNER HASH JOIN lu_channel_conns x ON [email protected]_id AND z.conn_type_id=x.conn_type_id 
    INNER HASH JOIN lu_conn_type ct ON ct.conn_type_id=x.conn_type_id AND ct.default_key_type_id=o.key_type_id' 
SET @param='@channel_id INT, @profile_key_id INT, @source_proc_group_id INT, @ret NVARCHAR(400) OUTPUT' 
EXEC sp_executesql @sql_str,@param,@channel_id,@profile_key_id,@source_proc_group_id,@ret OUTPUT 

à savoir cela donne:

SELECT @ret=MAX(o.optin_flag) AS optin_flag 
FROM optin_channel_1 o 
INNER HASH JOIN dbo.v_source_proc_id_by_group_id y 
    ON o.source_proc_id=y.source_proc_id 
    AND y.source_proc_group_id=5 
INNER HASH JOIN profile_conns z 
    ON z.profile_key_id=1 
    AND z.conn_key_type_id=o.key_type_id 
    AND z.conn_key_id=o.[key_id] 
    AND z.valid_to='01.01.3000' 
INNER HASH JOIN lu_channel_conns x 
    ON x.channel_id=1 
    AND z.conn_type_id=x.conn_type_id 
INNER HASH JOIN lu_conn_type ct 
    ON ct.conn_type_id=x.conn_type_id 
    AND ct.default_key_type_id=o.key_type_id 

Ces tables sont utilisées pour une base de données optin. optin_flag pourrait être 0 ou 1. Avec la dernière déclaration, je veux obtenir un 1 comme optin_flag de optin_channel_1 pour le donné channel_id=1 pour l'utilisateur avec profile_key_id=1, lorsque optin a été inséré dans la base de données par le processus appartenant à source_proc_group_id=5. J'espère que cela suffit pour comprendre ce qui se passe.

Est-ce la meilleure façon d'utiliser les CLUSTERED INDEX? Ou serait-il préférable de supprimer profile_key_id de l'index sur profile_conns et de mettre z.profile_key_id=1 dans une clause WHERE?

Peut-être y a-t-il un bien meilleur moyen d'optimiser cette sélection (les changements de schéma de base de données ne sont pas possibles, seulement les changements sur les index et les instructions de modification).

+1

Avez-vous des problèmes de performances avec la méthode actuelle ou pensez-vous ** avoir des problèmes de performances potentiels? – JNK

+0

Nous avons seulement prévu de faire des tests intensifs à l'avenir. Ce SQL dynamique n'est qu'une partie d'un énorme déclencheur sur une table et lorsque le déclencheur s'exécute, cette partie consomme plus de 50% de temps complet, car cette instruction s'exécute 30 fois pour 30 canaux. Je m'attends à une durée de déclenchement de 200-300ms. Mais maintenant il tourne environ une seconde. Bien sûr, vous ne connaissez pas le reste du déclencheur, mais au-dessus de SQL prend le plus de temps, donc je pense qu'il devrait être optimisé. – rabudde

Répondre

3

Sans connaître la taille des tables et le type de données stockées, elles sont difficiles à évaluer.

optin_channel_1 En supposant a beaucoup de données et profile_cons a beaucoup de données, je les opérations suivantes:

  • index cluster sur optin_channel_1 (key_id) ou key_type_id selon le champ a les valeurs les plus distinctes. (Puisque vous ne disposez pas d'un indice de couverture)
  • index cluster sur profile_conns (cons_key_id) ou cons_key_type_id en fonction de ce que vous avez choisi dans optin_channel_1
  • etc ...

En gros, si votre table profile_conns table n'a pas beaucoup de données, je mettrais l'index en cluster sur le champ "filtre" le plus fragmenté (je suspecte profile_key_id). Si la table contient beaucoup de données, je viserais une jointure hash/merge et ferais correspondre l'index cluster avec l'index cluster de la table optin_channel_1.

Je voudrais également réécrire la requête en tant que tel:

SELECT @ret = MAX(o.optin_flag) AS optin_flag 
    FROM optin_channel_1 o 
    JOIN dbo.v_source_proc_id_by_group_id y 
    ON o.source_proc_id = y.source_proc_id 
    JOIN profile_conns z 
    ON z.conn_key_type_id = o.key_type_id 
    AND z.conn_key_id = o.[key_id] 
    JOIN lu_channel_conns x 
    ON z.conn_type_id = x.conn_type_id 
    JOIN lu_conn_type ct 
    ON ct.conn_type_id = x.conn_type_id 
    AND ct.default_key_type_id=o.key_type_id 
WHERE y.source_proc_group_id = 5 
    AND z.profile_key_id = 1 
    AND x.channel_id = 1 
    AND z.valid_to = '01.01.3000' 

La requête a changé cette façon parce que:

  • Mettre les conditions de filtre dans la clause where vous montre quels sont les domaines pertinents pour viser une jointure hash/merge
  • Mettre des astuces de jointure est rarement une bonne idée. Il est très difficile de battre le gouverneur de la requête pour déterminer le meilleur plan de requête. Un mauvais plan indique généralement que vous avez un problème avec vos index/statistiques.

Afin Résumé:

  • petite table jointe à grande table ==> aller pour les boucles imbriquées & concentrer votre index cluster sur le champ « filtre » dans la petite table & le champ se joindre à la grande table.
  • grande table jointe à une grande table => go pour hachage/jointure par fusion et de mettre l'index cluster sur le champ correspondant sur les deux côtés
  • index multi-champs habituellement seulement une bonne idée quand ils sont « couverture », ce moyen tous les champs que vous interrogez sont inclus dans l'index. (ou sont inclus avec la clause include())
+1

+1 Pour supprimer l'indicateur 'JOIN' et déplacer les filtres vers la clause' WHERE'. – JNK

+0

Mon expérience est que mettre la condition dans la jointure peut parfois entraîner de meilleures performances. Dans l'une des bases de données que j'ai développées, il existe des tables de jointure pour les multi-valeurs (par exemple la liste cc sur un e-mail) avec plus de 100 millions d'enregistrements et la table principale environ 1 million. Si la condition de requête sur la jointure filtre la table de 100 millions d'enregistrements jusqu'à 100 enregistrements, l'exécution du filtre avant la jointure a donné lieu à de meilleures performances. SQL était préférable de faire ce filtre avant la jointure si la condition était dans la jointure et je ne pouvais pas obtenir cet effet avec des conseils de table. – Paparazzi

+0

@BalamBalam Cela ressemble à des statistiques incorrectes menant à un mauvais plan de requête. Le gouverneur de requête fait exactement le même plan de requête autant que je sache. Je vais faire un certain nombre de tests ce week-end pour vérifier. –

Questions connexes