Cette question est liée à ce fil de discussion .
Exécution de SQL Server 2008 Developer Edition sur mon poste de travail et un cluster de machines virtuelles à deux nœuds Enterprise Edition où je fais référence à "cluster alpha".
Le temps nécessaire pour supprimer des lignes avec une colonne varbinary (max) est directement lié à la longueur des données de cette colonne. Cela peut sembler intuitif au début, mais après enquête, cela ne correspond pas à ma compréhension de la façon dont SQL Server supprime réellement les lignes en général et traite ce type de données.
Le problème provient d'un problème de délai d'attente de suppression (> 30 secondes) que nous voyons dans notre application Web .NET, mais je l'ai simplifié pour le bien de cette discussion.
Lorsqu'un enregistrement est supprimé, SQL Server le marque comme un fantôme à nettoyer par une tâche de nettoyage des fantômes ultérieurement après la validation de la transaction (voir le blog de Paul Randal ). Dans un test supprimant trois lignes avec 16 Ko, 4 Mo et 50 Mo de données dans une colonne varbinary (max), respectivement, je vois cela se produire sur la page avec la partie en ligne des données, ainsi que dans la transaction Journal.
Ce qui me semble étrange, c'est que des verrous X sont placés sur toutes les pages de données LOB pendant la suppression et que les pages sont désallouées dans le PFS. Je vois cela dans le journal des transactions, ainsi qu'avec sp_lock
et les résultats du dm_db_index_operational_stats
DMV ( page_lock_count
).
Cela crée un goulot d'étranglement d'E / S sur mon poste de travail et notre cluster alpha si ces pages ne sont pas déjà dans le cache de tampon. En fait, le page_io_latch_wait_in_ms
même DMV correspond pratiquement à toute la durée de la suppression et page_io_latch_wait_count
correspond au nombre de pages verrouillées. Pour le fichier de 50 Mo sur mon poste de travail, cela se traduit par plus de 3 secondes lors du démarrage avec un cache tampon vide ( checkpoint
/ dbcc dropcleanbuffers
), et je ne doute pas que ce serait plus long pour une fragmentation importante et sous charge.
J'ai essayé de m'assurer qu'il ne s'agissait pas simplement d'allouer de l'espace dans le cache en prenant ce temps. J'ai lu 2 Go de données à partir d'autres lignes avant d'exécuter la suppression au lieu de la checkpoint
méthode, ce qui est plus que ce qui est alloué au processus SQL Server. Je ne sais pas si c'est un test valide ou non, car je ne sais pas comment SQL Server mélange les données. J'ai supposé que cela pousserait toujours l'ancien au profit du nouveau.
De plus, il ne modifie même pas les pages. Cela je peux voir avec dm_os_buffer_descriptors
. Les pages sont propres après la suppression, tandis que le nombre de pages modifiées est inférieur à 20 pour les trois suppressions petites, moyennes et grandes. J'ai également comparé la sortie de DBCC PAGE
pour un échantillonnage des pages recherchées, et il n'y a eu aucun changement (seul le ALLOCATED
bit a été supprimé de PFS). Il les désalloue simplement.
Pour prouver davantage que les recherches / désallocations de page sont à l'origine du problème, j'ai essayé le même test en utilisant une colonne filestream au lieu de varilla binaire (max). Les suppressions étaient à temps constant, quelle que soit la taille de LOB.
Donc, d'abord mes questions académiques:
- Pourquoi SQL Server doit-il rechercher toutes les pages de données LOB afin de les verrouiller X? Est-ce juste un détail de la façon dont les verrous sont représentés en mémoire (stockés avec la page en quelque sorte)? Cela fait que l'impact des E / S dépend fortement de la taille des données s'il n'est pas complètement mis en cache.
- Pourquoi le X se verrouille-t-il, juste pour les désallouer? N'est-il pas suffisant de verrouiller uniquement la feuille d'index avec la partie en ligne, car la désallocation n'a pas besoin de modifier les pages elles-mêmes? Existe-t-il un autre moyen d'accéder aux données LOB contre lesquelles le verrou protège?
- Pourquoi désallouer les pages à l'avance, étant donné qu'il existe déjà une tâche de fond dédiée à ce type de travail?
Et peut-être plus important, ma question pratique:
- Existe-t-il un moyen de faire fonctionner les suppressions différemment? Mon objectif est de supprimer le temps constant quelle que soit la taille, similaire à filestream, où tout nettoyage se produit en arrière-plan après coup. Est-ce une chose de configuration? Suis-je en train de stocker des choses étrangement?
Voici comment reproduire le test décrit (exécuté via la fenêtre de requête SSMS):
CREATE TABLE [T] (
[ID] [uniqueidentifier] NOT NULL PRIMARY KEY,
[Data] [varbinary](max) NULL
)
DECLARE @SmallID uniqueidentifier
DECLARE @MediumID uniqueidentifier
DECLARE @LargeID uniqueidentifier
SELECT @SmallID = NEWID(), @MediumID = NEWID(), @LargeID = NEWID()
-- May want to keep these IDs somewhere so you can use them in the deletes without var declaration
INSERT INTO [T] VALUES (@SmallID, CAST(REPLICATE(CAST('a' AS varchar(max)), 16 * 1024) AS varbinary(max)))
INSERT INTO [T] VALUES (@MediumID, CAST(REPLICATE(CAST('a' AS varchar(max)), 4 * 1024 * 1024) AS varbinary(max)))
INSERT INTO [T] VALUES (@LargeID, CAST(REPLICATE(CAST('a' AS varchar(max)), 50 * 1024 * 1024) AS varbinary(max)))
-- Do this before test
CHECKPOINT
DBCC DROPCLEANBUFFERS
BEGIN TRAN
-- Do one of these deletes to measure results or profile
DELETE FROM [T] WHERE ID = @SmallID
DELETE FROM [T] WHERE ID = @MediumID
DELETE FROM [T] WHERE ID = @LargeID
-- Do this after test
ROLLBACK
Voici quelques résultats du profilage des suppressions sur mon poste de travail:
| Type de colonne | Supprimer la taille | Durée (ms) | Lit | Écrit | CPU | -------------------------------------------------- ------------------ | VarBinary | 16 KB | 40 | 13 | 2 | 0 | | VarBinary | 4 Mo | 952 | 2318 | 2 | 0 | | VarBinary | 50 Mo | 2976 | 28594 | 1 | 62 | -------------------------------------------------- ------------------ | FileStream | 16 KB | 1 | 12 | 1 | 0 | | FileStream | 4 Mo | 0 | 9 | 0 | 0 | | FileStream | 50 Mo | 1 | 9 | 0 | 0 |
Nous ne pouvons pas nécessairement utiliser simplement filestream à la place parce que:
- Notre répartition de la taille des données ne le garantit pas.
- Dans la pratique, nous ajoutons des données dans de nombreux morceaux et filestream ne prend pas en charge les mises à jour partielles. Nous aurions besoin de concevoir autour de cela.
Mise à jour 1
Testé une théorie selon laquelle les données sont écrites dans le journal des transactions dans le cadre de la suppression, et cela ne semble pas être le cas. Suis-je en train de tester cela incorrectement? Voir ci-dessous.
SELECT MAX([Current LSN]) FROM fn_dblog(NULL, NULL)
--0000002f:000001d9:0001
BEGIN TRAN
DELETE FROM [T] WHERE ID = @ID
SELECT
SUM(
DATALENGTH([RowLog Contents 0]) +
DATALENGTH([RowLog Contents 1]) +
DATALENGTH([RowLog Contents 3]) +
DATALENGTH([RowLog Contents 4])
) [RowLog Contents Total],
SUM(
DATALENGTH([Log Record])
) [Log Record Total]
FROM fn_dblog(NULL, NULL)
WHERE [Current LSN] > '0000002f:000001d9:0001'
Pour un fichier de plus de 5 Mo, cela est retourné 1651 | 171860
.
De plus, je m'attendrais à ce que les pages elles-mêmes soient sales si les données étaient écrites dans le journal. Seules les désallocations semblent être enregistrées, ce qui correspond à ce qui est sale après la suppression.
Update 2
J'ai reçu une réponse de Paul Randal. Il a affirmé qu'il doit lire toutes les pages pour parcourir l'arborescence et trouver les pages à désallouer, et a déclaré qu'il n'y avait pas d'autre moyen de rechercher quelles pages. Ceci est une demi-réponse à 1 & 2 (mais n'explique pas la nécessité de verrouiller les données hors ligne, mais ce sont de petites pommes de terre).
La question 3 est toujours ouverte: pourquoi désallouer les pages à l'avance s'il existe déjà une tâche en arrière-plan pour nettoyer les suppressions?
Et bien sûr, la question la plus importante: existe-t-il un moyen d'atténuer directement (c'est-à-dire de ne pas contourner) ce comportement de suppression dépendant de la taille? Je pense que ce serait un problème plus courant, à moins que nous ne soyons vraiment les seuls à stocker et supprimer des lignes de 50 Mo dans SQL Server? Est-ce que tout le monde travaille autour de cela avec une forme de travail de collecte des ordures?