Wirklich interessante und schwierige Frage. Ich konnte keine offizielle Dokumentation dieses Verhaltens finden, und ich vermute, dass es keine gibt (obwohl ich es lieben würde, wenn mich jemand korrigiert!).
Meine Forschung lässt mich glauben, dass es der Planerstellungsschritt ist, der für diese Rennbedingung anfällig ist. Beachten Sie, dass ich Ihre Testabfragen eine Stunde lang fehlerfrei ausführen konnte. Wenn ich jedoch wiederholt Ihren Prozess starte, wird ein Teil der Zeit sofort ein Fehler angezeigt. Wenn ein Fehler auftritt, geschieht dies bei der Kompilierung des Plans immer sofort. Alternativ können Sie dem COUNT (*) in der Schleife "OPTION RECOMPILE" hinzufügen, um bei jedem Versuch einen neuen Plan zu kompilieren. Bei diesem Ansatz wird der Fehler fast sofort angezeigt, wenn ich Ihre Skripte ausgeführt habe.
Ich war in der Lage, den Fehler mit einer kontrollierten Reihe von Schritten zu reproduzieren, die den Fehler bei jedem Versuch zu treffen scheinen, wodurch die Notwendigkeit beseitigt wurde, eine Schleife einzurichten und sich auf Zufälligkeit zu verlassen.
Ich habe auch einen möglichen Fix eingebaut (um ALTER TABLE ... SWITCH zu verwenden), den Sie möglicherweise in Ihrer Produktionsumgebung ausprobieren können. Nun zu den Details!
Hier sind die Schritte zum Reproduzieren:
-- Instructions: Process each step one at a time, following any instructions in that step
-- (1) Initial setup: Clean any relics and create the test table
DBCC TRACEOFF(-1,3604,1200) WITH NO_INFOMSGS;
SET NOCOUNT ON;
IF @@TRANCOUNT > 0 ROLLBACK
IF object_id('test') IS NOT NULL
DROP TABLE test
IF object_id('TMP_test') IS NOT NULL
DROP TABLE TMP_test
IF object_id('test1') IS NOT NULL
DROP TABLE test1
CREATE TABLE test(Id INT IDENTITY CONSTRAINT PK_test PRIMARY KEY)
GO
INSERT test DEFAULT VALUES
GO 2000
-- (2) Run the COUNT(*)
-- This will acquire the Sch-S lock, and we use HOLDLOCK to retain that lock.
-- This simulates a race condition where this query is running at the time the first rename occurs.
BEGIN TRANSACTION
SELECT COUNT(*) FROM Test WITH (HOLDLOCK)
GO
-- (3) In another window, run the block of code that performs the renames
-- This will be blocked, waiting for a Sch-M lock on "test" until we complete step 4 below
CREATE TABLE TMP_test(Id INT PRIMARY KEY)
INSERT TMP_test SELECT * FROM test
EXEC sp_rename 'test', 'test1'
EXEC sp_rename 'TMP_test', 'test'
EXEC sp_rename 'test1', 'TMP_test'
DROP TABLE TMP_test
GO
-- (4) Now, commit the original COUNT(*) and immediately fire off the query again
-- We use OPTION (RECOMPILE) to make sure we need to compile a new query plan
-- The COMMIT releases the Sch-S lock, allowing (3) to acquire the Sch-M lock
-- This batch will now be waiting for the Sch-S lock again, on the same object_id,
-- but that object_id will no longer point to the correct object by the time the lock
-- is acquired.
COMMIT
SELECT COUNT(*) FROM Test OPTION (RECOMPILE)
GO
Mit diesen Schritten können wir etwas genauer untersuchen, was den Fehler verursacht. Zu diesem Zweck habe ich eine Ablaufverfolgung mit den folgenden Ereignissen ausgeführt: SP: StmtStarting, SP: StmtCompleted, Sperre: Erworben, Sperre: Freigegeben.
Was ich gefunden habe, ist, dass Folgendes in der richtigen Reihenfolge auftritt (wobei dazwischen einige Details weggelassen werden):
- Der erste COUNT (*) erhält eine Sch-S-Sperre
- Der erste COUNT (*) erhält einige Sperren für Statistiken (vermutlich, um einen Abfrageplan zu erstellen).
- Der erste COUNT (*) gibt eine Sch-S-Sperre frei (Ende der Planerstellung)
- Der erste COUNT (*) erhält eine IS-Sperre (Beginn der Ausführung) und wird dann erfolgreich ausgeführt
- Der sp_rename läuft bis zu dem Punkt, an dem eine Sch-M-Sperre erforderlich ist, und wird dann durch den ersten COUNT (*) blockiert, der noch nicht festgeschrieben ist
- Der erste COUNT (*) wird festgeschrieben
- Die sp_renames erwerben die Sch-M-Sperre
- Das zweite COUNT (*) fordert eine Sch-S-Sperre an und wird der Blockierungskette hinter dem sp_rename hinzugefügt
- Die sp_renames sind vollständig (zu diesem Zeitpunkt wartet der zweite COUNT (*) auf eine Sch-S-Sperre auf eine object_id, die nicht mehr die richtige object_id ist).
- Der zweite COUNT (*) erwirbt das Sch-S-Schloss
- Der zweite COUNT (*) erwirbt eine Sch-S-Sperre für sys.sysschobjs, vermutlich als Überprüfung der Integrität, da festgestellt wurde (oder bestätigt werden muss), dass etwas mit der Sch-S-Sperre, die für die Objekt-ID gewährt wurde, nicht stimmt sei "Test"
- Der Fehler "Test" des ungültigen Objektnamens wird ausgelöst
Wenn jedoch eine ähnliche Abfolge von Ereignissen auftritt, wenn keine Plankompilierung erforderlich ist (z. B. beim Ausführen Ihrer Schleife), ist SQL Server tatsächlich intelligent genug, um zu erkennen, dass sich die Objekt-ID geändert hat. Ich habe auch diesen Fall verfolgt und eine Situation gefunden, in der der zweite COUNT (*) die folgende Sequenz ausführte
- Erwirbt die Sch-S-Sperre (für die object_id, die früher "test" war)
- Erwirbt eine Sch-S-Sperre für sys.sysschobjs
- Löst diese beiden Sperren auf
- Erwirbt eine IS-Sperre für eine andere object_id; die neue object_id von "test"!
- Läuft erfolgreich bis zum Abschluss
Es sieht also so aus, als ob diese adaptive Logik vorhanden ist, wie Sie vermutet haben. Es sieht jedoch so aus, als ob es nur für die Ausführung von Abfragen und nicht für die Kompilierung von Abfragen vorhanden ist.
Wie versprochen, ist hier ein alternatives Snippet für Abschnitt (3), das eine Möglichkeit zum Umbenennen von Tabellen bietet, mit der das Problem der Parallelität behoben wird (zumindest auf meinem Computer!):
-- (3) In another window, run the block of code that performs the "renames"
-- Instead of sp_rename, we use ALTER TABLE...SWITCH
-- This appears to be a more robust way of performing the same logic
CREATE TABLE test1(Id INT PRIMARY KEY)
CREATE TABLE TMP_test(Id INT PRIMARY KEY)
INSERT TMP_test SELECT * FROM test
ALTER TABLE test SWITCH TO test1
ALTER TABLE TMP_test SWITCH TO test
ALTER TABLE test1 SWITCH TO TMP_test
DROP TABLE TMP_test
DROP TABLE test1
GO
Zum Schluss noch ein weiterer Link, den ich nützlich fand, um mir Gedanken darüber zu machen, wie man im Kontext von Tabellen, deren Namen sich ändern, über das Sperren nachdenken kann: