Si je comprends bien la demande, l'objectif est de supprimer des lots de lignes, tandis qu'en même temps, des opérations DML se produisent sur les lignes de la table. Le but est de supprimer un lot; cependant, si des lignes sous-jacentes contenues dans la plage définie par ledit lot sont verrouillées, nous devons ignorer ce lot et passer au lot suivant. Nous devons ensuite revenir à tous les lots qui n'ont pas été précédemment supprimés et réessayer notre logique de suppression d'origine. Nous devons répéter ce cycle jusqu'à ce que tous les lots de lignes requis soient supprimés.
Comme cela a été mentionné, il est raisonnable d'utiliser une indication READPAST et le niveau d'isolement READ COMMITTED (par défaut), afin d'ignorer les plages passées pouvant contenir des lignes bloquées. J'irai plus loin et recommanderai d'utiliser le niveau d'isolement SERIALISABLE et les suppressions de grignotage.
SQL Server utilise des verrous de plage de clés pour protéger une plage de lignes implicitement incluses dans un jeu d'enregistrements lu par une instruction Transact-SQL lors de l'utilisation du niveau d'isolation de transaction sérialisable ... en savoir plus ici:
https://technet.microsoft.com /en-US/library/ms191272(v=SQL.105).aspx
Avec les suppressions de grignotage, notre objectif est d'isoler une plage de lignes et de garantir qu'aucune modification ne se produira sur ces lignes pendant que nous les supprimons, c'est-à-dire que nous ne voulons pas de lectures ou d'insertions fantômes. Le niveau d'isolement sérialisable est destiné à résoudre ce problème.
Avant de présenter ma solution, je voudrais ajouter que je ne recommande pas de basculer le niveau d'isolement par défaut de votre base de données sur SERIALIZABLE ni de recommander que ma solution soit la meilleure. Je souhaite simplement le présenter et voir où nous pouvons aller d'ici.
Quelques notes d'entretien:
- La version de SQL Server que j'utilise est Microsoft SQL Server 2012 - 11.0.5343.0 (X64)
- Ma base de données de test utilise le modèle de récupération COMPLET
Pour commencer mon expérience, je vais mettre en place une base de données de test, un exemple de table, et je remplirai la table de 2 000 000 de lignes.
USE [master];
GO
SET NOCOUNT ON;
IF DATABASEPROPERTYEX (N'test', N'Version') > 0
BEGIN
ALTER DATABASE [test] SET SINGLE_USER
WITH ROLLBACK IMMEDIATE;
DROP DATABASE [test];
END
GO
-- Create the test database
CREATE DATABASE [test];
GO
-- Set the recovery model to FULL
ALTER DATABASE [test] SET RECOVERY FULL;
-- Create a FULL database backup
-- in order to ensure we are in fact using
-- the FULL recovery model
-- I pipe it to dev null for simplicity
BACKUP DATABASE [test]
TO DISK = N'nul';
GO
USE [test];
GO
-- Create our table
IF OBJECT_ID('dbo.tbl','U') IS NOT NULL
BEGIN
DROP TABLE dbo.tbl;
END;
CREATE TABLE dbo.tbl
(
c1 BIGINT IDENTITY (1,1) NOT NULL
, c2 INT NOT NULL
) ON [PRIMARY];
GO
-- Insert 2,000,000 rows
INSERT INTO dbo.tbl
SELECT TOP 2000
number
FROM
master..spt_values
ORDER BY
number
GO 1000
À ce stade, nous aurons besoin d'un ou plusieurs index sur lesquels les mécanismes de verrouillage du niveau d'isolement SERIALIZABLE peuvent agir.
-- Add a clustered index
CREATE UNIQUE CLUSTERED INDEX CIX_tbl_c1
ON dbo.tbl (c1);
GO
-- Add a non-clustered index
CREATE NONCLUSTERED INDEX IX_tbl_c2
ON dbo.tbl (c2);
GO
Maintenant, vérifions que nos 2 000 000 de lignes ont été créées
SELECT
COUNT(*)
FROM
tbl;
Nous avons donc notre base de données, notre table, nos index et nos lignes. Alors, configurons l'expérience pour supprimer les suppressions. Tout d'abord, nous devons décider de la meilleure façon de créer un mécanisme de suppression de grignotage typique.
DECLARE
@BatchSize INT = 100
, @LowestValue BIGINT = 20000
, @HighestValue BIGINT = 20010
, @DeletedRowsCount BIGINT = 0
, @RowCount BIGINT = 1;
SET NOCOUNT ON;
GO
WHILE @DeletedRowsCount < ( @HighestValue - @LowestValue )
BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION
DELETE
FROM
dbo.tbl
WHERE
c1 IN (
SELECT TOP (@BatchSize)
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN @LowestValue AND @HighestValue
ORDER BY
c1
);
SET @RowCount = ROWCOUNT_BIG();
COMMIT TRANSACTION;
SET @DeletedRowsCount += @RowCount;
WAITFOR DELAY '000:00:00.025';
CHECKPOINT;
END;
Comme vous pouvez le voir, j'ai placé la transaction explicite dans la boucle while. Si vous souhaitez limiter les débits de journaux, n'hésitez pas à le placer en dehors de la boucle. De plus, étant donné que nous sommes dans le modèle de récupération COMPLET, vous souhaiterez peut-être créer des sauvegardes du journal des transactions plus souvent lors de l'exécution de vos opérations de suppression de grignotage, afin de vous assurer que votre journal des transactions ne puisse pas croître de manière scandaleuse.
Donc, j'ai quelques objectifs avec cette configuration. Tout d'abord, je veux mes verrous de plage de clés; donc, j'essaie de garder les lots aussi petits que possible. Je ne veux pas non plus avoir un impact négatif sur la concurrence sur ma table "gigantesque"; donc je veux prendre mes serrures et les laisser le plus vite possible. Je vous recommande donc de réduire la taille de vos lots.
Maintenant, je veux fournir un exemple très court de cette routine de suppression en action. Nous devons ouvrir une nouvelle fenêtre dans SSMS et supprimer une ligne de notre tableau. Je vais le faire dans une transaction implicite en utilisant le niveau d'isolement READ COMMITTED par défaut.
DELETE FROM
dbo.tbl
WHERE
c1 = 20005;
Cette ligne a-t-elle été supprimée?
SELECT
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN 20000 AND 20010;
Oui, il a été supprimé.
Maintenant, pour voir nos verrous, ouvrons une nouvelle fenêtre dans SSMS et ajoutons un ou deux extraits de code. J'utilise sp_whoisactive d'Adam Mechanic, qui peut être trouvé ici: sp_whoisactive
SELECT
DB_NAME(resource_database_id) AS DatabaseName
, resource_type
, request_mode
FROM
sys.dm_tran_locks
WHERE
DB_NAME(resource_database_id) = 'test'
AND resource_type = 'KEY'
ORDER BY
request_mode;
-- Our insert
sp_lock 55;
-- Our deletions
sp_lock 52;
-- Our active sessions
sp_whoisactive;
Maintenant, nous sommes prêts à commencer. Dans une nouvelle fenêtre SSMS, commençons une transaction explicite qui tentera de réinsérer la ligne que nous avons supprimée. En même temps, nous lancerons notre opération de suppression de grignotage.
Le code d'insertion:
BEGIN TRANSACTION
SET IDENTITY_INSERT dbo.tbl ON;
INSERT INTO dbo.tbl
( c1 , c2 )
VALUES
( 20005 , 1 );
SET IDENTITY_INSERT dbo.tbl OFF;
--COMMIT TRANSACTION;
Commençons les deux opérations en commençant par l'insertion et suivies de nos suppressions. Nous pouvons voir les serrures à clés et les serrures exclusives.
L'insert a généré ces verrous:
La suppression / sélection de grignotage contient ces verrous:
Notre insert bloque notre suppression comme prévu:
Maintenant, commettons la transaction d'insertion et voyons ce qui se passe.
Et comme prévu, toutes les transactions sont terminées. Maintenant, nous devons vérifier si l'insertion était un fantôme ou si l'opération de suppression l'a également supprimée.
SELECT
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN 20000 AND 20015;
En fait, l'encart a été supprimé; ainsi, aucun insert fantôme n'était autorisé.
Donc, en conclusion, je pense que la véritable intention de cet exercice n'est pas d'essayer de suivre chaque verrou de ligne, de page ou de table et de déterminer si un élément d'un lot est verrouillé et nécessiterait donc notre opération de suppression pour attendez. C'était peut-être l'intention des intervenants; cependant, cette tâche est herculéenne et pratiquement impossible, voire impossible. Le véritable objectif est de garantir qu'aucun phénomène indésirable ne se produit une fois que nous avons isolé la plage de notre lot avec des verrous qui nous sont propres, puis que nous supprimons le lot. Le niveau d'isolement SERIALISABLE atteint cet objectif. La clé est de garder vos petits morceaux, votre journal des transactions sous contrôle et d'éliminer les phénomènes indésirables.
Si vous voulez de la vitesse, ne construisez pas de tables gigantesques qui ne peuvent pas être partitionnées et ne pouvez donc pas utiliser la commutation de partition pour les résultats les plus rapides. La clé de la vitesse est le partitionnement et le parallélisme; la clé de la souffrance est le grignotage et le verrouillage des vies.
S'il vous plait, faite moi part de votre avis.
J'ai créé quelques exemples supplémentaires du niveau d'isolement SERIALIZABLE en action. Ils devraient être disponibles sur les liens ci-dessous.
Supprimer l'opération
Insérer une opération
Opérations d'égalité - Verrouillage de plage de clés sur les prochaines valeurs clés
Opérations d'égalité - extraction singleton de données existantes
Opérations d'égalité - extraction singleton de données inexistantes
Opérations d'inégalité - Verrouillage de plage de clés sur la plage et valeurs de clé suivantes