Minimiser les lectures indexées avec des critères complexes

12

J'optimise une base de données Firebird 2.5 de tickets de travail. Ils sont stockés dans une table déclarée comme telle:

CREATE TABLE TICKETS (
  TICKET_ID id PRIMARY KEY,
  JOB_ID id,
  ACTION_ID id,
  STATUS str256 DEFAULT 'Pending'
);

Je souhaite généralement trouver le premier ticket qui n'a pas été traité et qui est en cours Pending.

Ma boucle de traitement serait:

  1. Récupérer le 1er ticket où Pending
  2. Travaillez avec Ticket.
  3. Mettre à jour l'état du ticket => Complete
  4. Répéter.

Rien d'extraordinaire. Si je regarde la base de données pendant l'exécution de cette boucle, je vois le nombre de lectures indexées grimper pour chaque itération. Les performances ne semblent pas se dégrader terriblement, ce que je peux dire, mais la machine sur laquelle je teste est assez rapide. Cependant, j'ai reçu des rapports de dégradation des performances au fil du temps de certains de mes utilisateurs.

J'ai un index Status, mais il semble toujours qu'il balaye la Ticket_Idcolonne à chaque itération. Il semble que j'oublie quelque chose, mais je ne sais pas quoi. Le nombre croissant de lectures indexées pour quelque chose comme cela est-il attendu, ou l'index se comporte-t-il d'une manière ou d'une autre?

- Modifications pour commentaires -

Dans Firebird, vous limitez la récupération des lignes comme:

Select First 1
  Job_ID, Ticket_Id
From
  Tickets
Where
  Status = 'Pending'

Donc quand je dis "premier", je lui demande juste un record limité où Status = 'Pending'.

gddc
la source
Que voulez-vous dire par «premier» dans «Récupérer le 1er ticket où« En attente »» ?
ypercubeᵀᴹ
Si "premier" signifie le plus petit ticket_id, vous avez probablement besoin d'un index sur(status, ticket_id)
ypercubeᵀᴹ
Et dans quelle mesure êtes-vous sûr que la dégradation des performances est causée par cette procédure et non par d'autres requêtes / instructions?
ypercubeᵀᴹ
@ypercube - Non, je ne suis pas certain que c'est là que se situe la dégradation des performances. C'est pourquoi ma question était "dois-je me préoccuper de cela, ou s'agit-il d'un comportement normal d'un index?". C'est quelque chose que j'ai remarqué lors de la surveillance de la base de données, et je l'ai trouvé inattendu. Je ne m'attendrais pas à ce qu'il continue à analyser les lignes précédentes lorsque je fournis une clause where par rapport à une colonne indexée. FWIW, la modification de l'index pour inclure ticket_idréellement effectué moins bien que simplement avoir le statut indexé.
gddc
Le idtype de données est-il un domaine que vous avez défini?
a_horse_with_no_name

Réponses:

1

La dégradation au fil du temps se produit en raison du nombre accru d'articles qui sont dans l'état "Terminé". Pensez-y une seconde - vous n'obtiendrez aucune dégradation des performances lors des tests car vous avez probablement un petit nombre de lignes avec le statut "Terminé". Mais en production, ils peuvent avoir des millions de lignes avec le statut "Complet" et ce nombre augmentera avec le temps. Cela rend essentiellement votre index sur le statut de moins en moins utile au fil du temps. En tant que tel, la base de données décide probablement que, parce que Status a presque toujours la valeur «Complete», elle analysera simplement la table au lieu d'utiliser l'index.

Dans SQL Server (et peut-être d'autres SGBDR?), Cela peut être résolu en utilisant des index filtrés. Dans SQL Server, vous ajouteriez une condition WHERE à la fin de votre définition d'index pour dire «appliquer cet index uniquement aux enregistrements avec un état <>« Terminé »». Ensuite, toute requête utilisant ce prédicat utilisera très probablement l'index sur la petite quantité d'enregistrements non définie sur "Terminé". Cependant, sur la base de la documentation ici: http://www.firebirdsql.org/refdocs/langrefupd25-ddl-index.html , il ne semble pas que Firebird supporte les index filtrés.

Une solution de contournement consiste à placer des enregistrements «complets» dans une table ArchiveTickets. Créez une table avec la même définition exacte (mais sans aucun ID généré automatiquement) que votre table Tickets et conservez des lignes entre elles en poussant les enregistrements «Complete» vers la table ArchiveTickets. L'index sur votre table Tickets sera alors sur un nombre beaucoup plus faible d'enregistrements et sera beaucoup plus performant. Cela signifiera probablement que vous devrez modifier tous les rapports, etc. qui font référence aux tickets 'Complete' pour pointer vers la table Archive ou effectuer une UNION à la fois sur Tickets et ArchiveTickets. Cela aura l'avantage non seulement d'être rapide, mais cela signifiera également que vous pouvez créer des index spécifiques pour la table ArchiveTickets pour la rendre plus performante pour d'autres requêtes (par exemple:

Vous devriez vous en préoccuper si votre production va aller dans les milliers de lignes. Les performances se dégraderont avec le temps et auront un impact négatif sur votre expérience utilisateur.

tache
la source
0

Que les performances soient affectées ou non dépendra du volume de données et de la capacité de la machine. Compte tenu de la capacité du matériel moderne, il est difficile d'imaginer un volume de ventes de billets qui ne pourrait pas être géré par la conception que vous décrivez. Cependant, il y a des changements que je recommanderais pour l'exactitude et pourraient améliorer les performances comme avantage secondaire.

Votre première requête en attente n'est pas déterministe. D'abord selon quel ordre? Une table SQL n'a pas d'ordre intrinsèque; le First 1hack vous donne juste un premier arbitraire. Pour le rendre déterministe, pourquoi ne pas traiter les travaux en attente dans l'ordre Job_ID?

Si vous avez deux index {Job_ID} et {Status, Job_ID}, cette requête renvoie une ligne de manière prévisible et efficace:

Select Job_ID, Ticket_Id
From   Tickets
Where Job_ID = ( 
  select min(Job_ID) from Tickets 
  where Status = 'Pending'
);

Je ne suis pas un utilisateur Firebird, vous devrez donc vérifier le plan de requête, mais il devrait être efficace car la sous-requête ne fait référence qu'au deuxième index, produit une valeur pour le premier. (Il peut y avoir d'autres astuces d'efficacité à votre disposition. Vous pourriez être en mesure d'organiser la table physique comme une arborescence B +, ou avoir accès à un row_id caché, par exemple.)

L'autre changement que je ferais pour être correct est de créer Statusun seul octet contraint et de laisser l'application fournir la chaîne "En attente". Cela évitera des Statusvaleurs erronées et rendra probablement l'indice plus petit dans le marché. Quelque chose comme:

CREATE TABLE TICKETS (
  TICKET_ID id PRIMARY KEY,
  JOB_ID id,
  ACTION_ID id,
  STATUS char(1) not NULL 
     DEFAULT 'P'
     CHECK( STATUS in ('P', 'C', 'X') ) -- whatever the domain is
);

Bien sûr, vous pouvez utiliser une vue (ou peut-être une colonne dérivée) pour fournir les chaînes canoniques pour Status.

James K. Lowden
la source