Minimierung indizierter Lesevorgänge mit komplexen Kriterien

12

Ich optimiere eine Firebird 2.5-Datenbank mit Arbeitstickets. Sie werden in einer als solche deklarierten Tabelle gespeichert:

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

Ich möchte in der Regel das erste Ticket finden, das noch nicht bearbeitet wurde und sich im PendingStatus befindet.

Meine Verarbeitungsschleife wäre:

  1. Holen Sie sich das erste Ticket wo Pending
  2. Arbeiten Sie mit Ticket.
  3. Ticketstatus aktualisieren => Complete
  4. Wiederholen.

Nichts Besonderes. Wenn ich die Datenbank beobachte, während diese Schleife läuft, sehe ich die Anzahl der indizierten Lesezugriffe für jede Iteration. Die Leistung scheint sich nicht zu verschlechtern, aber der Rechner, auf dem ich teste, ist ziemlich schnell. Ich habe jedoch von einigen meiner Benutzer Berichte über Leistungseinbußen im Laufe der Zeit erhalten.

Ich habe einen Index für Status, aber es scheint immer noch, als würde er die Ticket_IdSpalte bei jeder Iteration durchsuchen . Es scheint, als würde ich etwas übersehen, aber ich weiß nicht, was. Wird die steigende Anzahl indizierter Lesevorgänge für so etwas erwartet, oder verhält sich der Index in irgendeiner Weise schlecht?

- Bearbeitungen für Kommentare -

In Firebird beschränken Sie den Zeilenabruf wie folgt:

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

Wenn ich also "zuerst" sage, frage ich nur nach einem begrenzten Datensatz, wo Status = 'Pending'.

gddc
quelle
Was meinst du mit "first" in "Retrieve 1st Ticket where 'Pending'" ?
ypercubeᵀᴹ
Wenn "zuerst" "am kleinsten" bedeutet ticket_id, benötigen Sie wahrscheinlich einen Index für(status, ticket_id)
ypercubeᵀᴹ 24.09.12
Und wie sicher sind Sie, dass der Leistungsabfall durch dieses Verfahren und nicht durch andere Abfragen / Anweisungen verursacht wird?
ypercubeᵀᴹ
@ypercube - Nein, ich bin mir nicht sicher, wo der Leistungsabfall ist. Deshalb lautete meine Frage: "Muss ich mich darum kümmern, oder ist es das normale Verhalten eines Index?" Dies ist mir beim Überwachen der Datenbank aufgefallen und ich habe es als unerwartet empfunden. Ich würde nicht erwarten, dass die vorhergehenden Zeilen weiterhin durchsucht werden, wenn ich eine where-Klausel für eine indizierte Spalte gebe. FWIW: Wenn der Index so geändert wird, dass er ticket_idtatsächlich einschließt, ist dies schlechter, als wenn nur der Status indiziert wird.
Gddc
Ist id(der Datentyp) eine von Ihnen definierte Domain?
a_horse_with_no_name

Antworten:

1

Die Verschlechterung im Laufe der Zeit tritt aufgrund der erhöhten Anzahl von Elementen auf, die sich im Status "Vollständig" befinden. Denken Sie eine Sekunde darüber nach - Sie werden beim Testen keine Leistungseinbußen feststellen, da Sie wahrscheinlich eine kleine Anzahl von Zeilen mit dem Status "Abgeschlossen" haben. In der Produktion können jedoch Millionen Zeilen mit dem Status "Vollständig" vorhanden sein, und diese Anzahl wird mit der Zeit zunehmen. Dies führt im Wesentlichen dazu, dass Ihr Index zum Status im Laufe der Zeit immer weniger nützlich wird. Aus diesem Grund entscheidet die Datenbank wahrscheinlich nur, dass der Status fast immer den Wert 'Vollständig' hat und die Tabelle nur durchsucht wird, anstatt den Index zu verwenden.

In SQL Server (und möglicherweise in anderen RDBMS?) Kann dies mithilfe von gefilterten Indizes umgangen werden. In SQL Server würden Sie dem Ende Ihrer Indexdefinition eine WHERE-Bedingung hinzufügen, die besagt: "Diesen Index nur auf Datensätze mit dem Status <> 'Abgeschlossen' anwenden". Dann verwendet jede Abfrage, die dieses Prädikat verwendet, höchstwahrscheinlich den Index für die kleine Menge von Datensätzen, die nicht auf "Vollständig" gesetzt sind. Basierend auf der Dokumentation hier: http://www.firebirdsql.org/refdocs/langrefupd25-ddl-index.html sieht es jedoch nicht so aus, als ob Firebird gefilterte Indizes unterstützt.

Eine Problemumgehung besteht darin, 'Complete'-Datensätze in eine ArchiveTickets-Tabelle einzufügen. Erstellen Sie eine Tabelle mit genau derselben Definition (jedoch ohne automatisch generierte ID) wie Ihre Tickets-Tabelle und pflegen Sie die Zeilen zwischen ihnen, indem Sie die Option 'Vollständige' Datensätze in die ArchiveTickets-Tabelle übernehmen. Der Index auf Ihrem Tickettisch wird sich dann über eine viel kleinere Anzahl von Datensätzen erstrecken und eine viel höhere Leistung aufweisen. Dies bedeutet wahrscheinlich, dass Sie alle Berichte usw. ändern müssen, die auf 'Complete'-Tickets verweisen, um auf die Archivtabelle zu verweisen, oder eine UNION für Tickets und ArchiveTickets ausführen. Dies hat den Vorteil, dass Sie nicht nur schnell sind, sondern auch bestimmte Indizes für die ArchiveTickets-Tabelle erstellen können, um die Leistung für andere Abfragen zu verbessern (z. B .:

Sie sollten sich darüber Gedanken machen, wenn Ihre Produktion in die Tausende von Reihen geht. Die Leistung verschlechtert sich mit der Zeit und wirkt sich negativ auf die Benutzerfreundlichkeit aus.

blobbles
quelle
0

Ob die Leistung beeinträchtigt wird oder nicht, hängt vom Datenvolumen und der Maschinenkapazität ab. Angesichts der Kapazität moderner Hardware ist es schwer vorstellbar, dass das Ticketverkaufsvolumen mit dem von Ihnen beschriebenen Design nicht bewältigt werden kann. Es gibt jedoch Änderungen, die ich aus Gründen der Korrektheit empfehlen würde, und die die Leistung möglicherweise als sekundären Vorteil verbessern.

Ihre erste ausstehende Abfrage ist nicht deterministisch. Zuerst in welcher Reihenfolge? Eine SQL-Tabelle hat keine eigene Reihenfolge. der First 1Hack gibt dir nur einen willkürlichen ersten. Warum nicht ausstehende Jobs in der Reihenfolge Job_ID verarbeiten, um sie deterministisch zu machen?

Wenn Sie über zwei Indizes {Job_ID} und {Status, Job_ID} verfügen, gibt diese Abfrage vorhersehbar und effizient eine Zeile zurück:

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

Ich bin kein Firebird-Benutzer, daher müssen Sie den Abfrageplan überprüfen. Er sollte jedoch effizient sein, da die Unterabfrage nur auf den zweiten Index verweist und einen Wert für den ersten Index erzeugt. (Möglicherweise stehen Ihnen andere Effizienztricks zur Verfügung. Sie können die physische Tabelle möglicherweise als B + -Baum organisieren oder beispielsweise auf eine ausgeblendete row_id zugreifen.)

Die andere Änderung, die ich aus Gründen der Korrektheit vornehmen würde, besteht darin, Statusein einzelnes, eingeschränktes Byte zu erstellen und die Anwendung die Zeichenfolge "Ausstehend" bereitstellen zu lassen. Dies schützt vor fehlerhaften StatusWerten und verringert wahrscheinlich den Index im Handel. Etwas wie:

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

Natürlich können Sie eine Ansicht (oder eine abgeleitete Spalte) verwenden, um die kanonischen Zeichenfolgen für Status anzugeben.

James K. Lowden
quelle