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:
- Récupérer le 1er ticket où
Pending
- Travaillez avec Ticket.
- Mettre à jour l'état du ticket =>
Complete
- 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_Id
colonne à 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'
.
ticket_id
, vous avez probablement besoin d'un index sur(status, ticket_id)
ticket_id
réellement effectué moins bien que simplement avoir le statut indexé.id
type de données est-il un domaine que vous avez défini?Réponses:
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.
la source
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 1
hack 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:
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
Status
un seul octet contraint et de laisser l'application fournir la chaîne "En attente". Cela évitera desStatus
valeurs erronées et rendra probablement l'indice plus petit dans le marché. Quelque chose comme:Bien sûr, vous pouvez utiliser une vue (ou peut-être une colonne dérivée) pour fournir les chaînes canoniques pour Status.
la source