SQL Server benutzerdefinierte Zähler gespeicherte Prozedur Erstellen von Dupes

8

Ich habe eine Stored Procedure erstellt, um die Ratenbegrenzung für meine API zu implementieren, diese wird ca. 5-10k mal pro Sekunde aufgerufen und jeden Tag bemerke ich Duples in der Counter-Tabelle.

Es wird der übergebene API-Schlüssel nachgeschlagen und dann die Zählertabelle mit der Kombination aus ID und Datum mit einem "UPSERT" überprüft. Wenn ein Ergebnis gefunden wird, wird UPDATE [count] +1 ausgeführt, andernfalls wird INSERT angegeben eine neue Zeile.

Es gibt keinen Primärschlüssel in der Zählertabelle.

Hier ist die gespeicherte Prozedur:

%Vor%

Ich denke auch, dass einige Sperren nach der Einführung dieses SP auftreten.

Die Betrogenen machen nichts kaputt, aber ich bin neugierig, ob etwas grundsätzlich falsch mit meinem Code ist oder ob ich eine Einschränkung in der Tabelle einrichten sollte, um dies zu verhindern. Danke

Update 6/23/17: Ich habe die MERGE-Anweisung gelöscht und versucht, @@ ROWCOUNT zu verwenden, aber es hat auch Duples verursacht

%Vor%     
bfritz 18.06.2017, 04:27
quelle

4 Antworten

6

Ein HOLDLOCK Hinweis auf die update Anweisung vermeidet die Race Condition. Um Deadlocks zu vermeiden, schlage ich einen gruppierten zusammengesetzten Primärschlüssel (oder eindeutigen Index) für ID und date vor.

Das folgende Beispiel enthält diese Änderungen und verwendet die SET <variable> = <column> = <expression> -Form der SET -Klausel, um die Notwendigkeit des nachfolgenden SELECT des letzten Zählerwerts zu vermeiden und dadurch die Leistung zu verbessern.

%Vor%     
Dan Guzman 23.06.2017, 19:00
quelle
2

Wahrscheinlich nicht die Antwort, die Sie suchen, aber für einen Rate-limitierenden Zähler würde ich einen Cache wie Redis in einem verwenden Middleware vor dem Zugriff auf die API. Leistungsmäßig ist es ziemlich gut, da Redis kein Problem mit der Last haben würde und Ihre DB nicht beeinflusst wird.

Und wenn Sie eine Historie von Treffern pro API-Schlüssel pro Tag in SQL behalten möchten, führen Sie eine tägliche Aufgabe aus, um die Zählungen von gestern von Redis nach SQL zu importieren.

Der Datensatz wäre klein genug, um eine Redis-Instanz zu erhalten, die buchstäblich nichts kostet (oder schließt).

    
drkbrd 23.06.2017 19:46
quelle
1

Es wird die Merge-Anweisung, die mit sich selbst in eine Race-Bedingung gerät, d. h. Ihre API wird vom selben Client aufgerufen und beide Male findet die Merge-Anweisung keine Zeile, fügt also eine ein. Merge ist keine atomare Operation, obwohl es vernünftig ist, davon auszugehen. Zum Beispiel finden Sie diesen Fehlerbericht für SQL 2008 über Merge Deadlocks verursacht, sagte das SQL Server-Team, dass dies beabsichtigt ist.

Von Ihrem Post aus gesehen ist das unmittelbare Problem, dass Ihre Kunden möglicherweise eine kleine Anzahl kostenloser Zugriffe auf Ihre API erhalten. Wenn zum Beispiel zwei Anfragen kommen und keine Zeile sehen, beginnen Sie mit zwei Zeilen mit einer Anzahl von 1, wenn Sie tatsächlich eine Zeile mit einer Anzahl von 2 wünschen und der Client am Ende einen freien API-Treffer erhalten könnte. Wenn drei Anfragen überkreuzt werden, erhalten Sie drei Zeilen mit einer Anzahl von 1, und Sie erhalten 2 kostenlose API - Treffer usw.

Bearbeiten

So wie Ihr Link vorschlägt, dass Sie zwei Kategorien von Optionen haben, die Sie untersuchen könnten, versuchen Sie zunächst einmal, dies in SQL Server zu arbeiten, zweitens andere architektonische Lösungen.

Bei der SQL-Option würde ich die Zusammenführung abbrechen und erwägen, Ihre Kunden im Voraus oder zu einem späteren Zeitpunkt einige Tage im Voraus zu füllen. Dies würde Ihnen ein einziges Update anstelle der Zusammenführung / Aktualisierung hinterlassen und einfügen. Dann können Sie bestätigen, dass sowohl Ihr Update als auch Ihr Select vollständig optimiert sind, dh über den notwendigen Index verfügen und keine Scans verursachen. Als Nächstes können Sie sich die Sperrfunktion ansehen, sodass Sie nur auf Zeilenebene sperren. Weitere Informationen finden Sie unter dies für weitere Informationen. Für die Auswahl könnten Sie auch mit NOLOCK schauen, was bedeutet, dass Sie leicht falsche Daten bekommen könnten, aber das sollte in Ihrem Fall keine Rolle spielen, Sie werden ein WHERE verwenden, das immer auch eine einzelne Zeile als Ziel hat.

Für die Nicht-SQL-Optionen, da Ihr Link besagt, dass Sie Dinge in die Warteschlange stellen könnten, wären dies natürlich die Aktualisierungen / Einfügungen, so dass Ihre Selects alte Daten sehen würden. Dies kann oder kann nicht akzeptabel sein, je nachdem wie weit sie voneinander entfernt sind, obwohl Sie dies als eine "eventuell konsistente" Lösung haben könnten, wenn Sie strikt sein und Extra-Gebühren oder API-Hits am nächsten Tag oder etwas ausgeben möchten. Sie können auch Caching-Optionen anzeigen, um die Anzahl zu speichern. Dies würde komplexer werden, wenn Ihre App verteilt wird, aber es gibt Caching-Lösungen dafür. Wenn du mit Caching gegangen bist, könntest du dich dafür entscheiden, nichts weiter zu machen, aber dann würdest du möglicherweise eine Menge kostenloser Treffer verschenken, wenn deine Seite ausfällt, aber du hättest wahrscheinlich größere Probleme, um die du dich dann kümmern musst!

    
Matt 18.06.2017 05:31
quelle
0

Haben Sie auf hohem Niveau erwogen, das folgende Szenario zu verfolgen?

Restrukturierung: Setzen Sie den Primärschlüssel für Ihre Tabelle auf eine Kombination aus (ID, Datum). Vielleicht sogar besser, verwenden Sie einfach den API-Schlüssel selbst anstelle der willkürlichen ID, die Sie ihm zuweisen.

Abfrage A: Entspricht die SQL Server-Entsprechung von "INSERT IGNORE" (es gibt semantische Entsprechungen für SQL Server basierend auf einer Google-Suche) mit den Werten (ID, TODAY (), 1). Sie möchten auch eine WHERE-Klausel angeben, die überprüft, ob die ID tatsächlich in Ihrer API / Limit-Tabelle vorhanden ist.

Abfrage B: Aktualisieren Sie die Zeile mit (ID, TODAY ()) als Primärschlüssel, setzen Sie count: = count + 1, und führen Sie in genau derselben Abfrage einen inneren Join mit Ihrer Limit-Tabelle durch, so dass in der where-Klausel Sie können angeben, dass Sie die Anzahl nur aktualisieren, wenn die Anzahl & lt; Grenze.

Wenn die meisten Ihrer Anfragen gültige API-Anfragen oder Anfragen mit begrenzter Rate sind, würde ich bei jeder Anfrage Abfragen in der folgenden Reihenfolge ausführen:

%Vor%

Wenn die meisten Ihrer Anfragen ungültige API-Anfragen sind, würde ich Folgendes tun:

%Vor%     
Kurtiss Hare 23.06.2017 20:12
quelle