web-dev-qa-db-fra.com

Supprimer la requête sans supprimer les données

J'exécute un service Web qui se connecte à une base de données SQL Server 2012. Lorsque vous exécutez plusieurs requêtes de suppression dans un court laps de temps (environ 5 en une seconde), il semble aléatoire celles qui sont réellement exécutées. Lorsqu'elles sont exécutées une par une ou en mettant un délai de 0,5 seconde entre chaque exécution, elles fonctionnent parfaitement.

En regardant le profileur SQL, toutes les requêtes s'affichent sous la forme RPC:Completed même s'ils n'ont pas réellement supprimé la ligne du tableau. J'ai vérifié la table et les données étaient toujours là, puis j'ai copié et collé la requête du profileur dans SSMS et cela a affecté une ligne et je l'ai supprimée.

Je suppose donc que le service Web fonctionne correctement et que le problème se situe du côté de la base de données. Existe-t-il un moyen dans le profileur de voir si la requête a réussi? Et qu'est-ce qui pourrait faire que cela n'affecte pas réellement la ligne?

Pas de déclencheurs. Exécuter la même requête uniquement avec des paramètres différents. Seules les données changent généralement de manière séquentielle. Aucune autre requête n'est en cours d'exécution sur la base de données pour l'instant.

J'ai ajouté la journalisation du site client, il renvoie en fait 1 s'il supprime la ligne et 0 s'il ne supprime pas la ligne. Cependant, même lorsqu'il apparaît comme 0, le profileur montre qu'il a exécuté la requête mais ne semble pas l'avoir affectée. Et lorsque j'exécute la requête via SSMS, cela affecte la ligne.

Ne pas recevoir d'erreurs du service Web et la requête s'exécute correctement via SSMS. Semble seulement ne pas supprimer lorsqu'il est exécuté plusieurs fois de suite rapidement. Je suis d'accord qu'il cible très probablement différentes lignes, mais je ne vois pas comment cela se produit lorsqu'il fonctionne correctement dans SSMS.

structure et requête de table

CREATE TABLE dbo.ContractDates2HumanAssets
(
    iContractDate2HumanAssetID int IDENTITY(1,1) NOT NULL,
    iContractDateID int NOT NULL,
    iHumanAssetID int NOT NULL,
    cCategory nvarchar(256) NOT NULL,
    cHR_x0020_ID nvarchar(256) NOT NULL,
    cCD_x0020_ID nvarchar(256) NOT NULL,
     CONSTRAINT PK_ContractDates2HumanAssets PRIMARY KEY CLUSTERED 
    (
        iContractDate2HumanAssetID ASC
    ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY];
GO

CREATE TABLE dbo.ContractDates
(
    iContractDateID int IDENTITY(1,1) NOT NULL,
    iContractID int NOT NULL,
    dDate datetime NOT NULL,
    cResource nvarchar(256) NOT NULL,
    cCD_x0020_ID nvarchar(256) NOT NULL,
    CONSTRAINT PK_ContractDates PRIMARY KEY CLUSTERED 
    (
        iContractDateID ASC
    ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY];

exec sp_executesql N'DELETE FROM ContractDates2HumanAssets 
WHERE iContractDateID IN ((SELECT ContractDates.iContractDateID 
FROM ContractDates2HumanAssets 
INNER JOIN ContractDates ON ContractDates.iContractDateID = ContractDates2HumanAssets.iContractDateID
WHERE iHumanAssetID = @humanid AND iContractID = (SELECT iContractID FROM Contracts WHERE cContractNo = @id) AND dDate = @newDate))',N'@id nvarchar(6),@newDate nvarchar(10),@humanid int',@id=N'999111',@newDate=N'2018-03-16',@humanid=82

C'est la requête qui apparaît dans le profileur. Après avoir vérifié le tableau, cette ligne est toujours là. Mais en copiant et collant cela dans SSMS et en l'exécutant, il revient avec 1 ligne affectée et est supprimé.

Données de table

SELECT * 
FROM ContractDates2HumanAssets 
WHERE iContractDateID IN
(
    (
        SELECT ContractDates.iContractDateID 
        FROM ContractDates2HumanAssets 
        INNER JOIN ContractDates 
            ON ContractDates.iContractDateID = ContractDates2HumanAssets.iContractDateID 
        WHERE 
            iHumanAssetID = 82 
            AND iContractID = 
            (
                SELECT iContractID 
                FROM Contracts 
                WHERE cContractNo = N'999111'
            )
    )
);

Les resultats:

╔════════════════════════════╦═════════════════╦══ ═════════════╦═══════════╦══════════════╦═════════ ═════╗ 
 ║ iContractDate2HumanAssetID ║ iContractDateID ║ iHumanAssetID ║ cCategory ║ cHR_x0020_ID ║ cCD_x0020_ID ║ 
 ╠════════════════════ ════════╬═════════════════╬═══════════════╬═══════ ════╬══════════════╬══════════════╣ 
 ║ 102538 ║ 113369 ║ 82 ║ ║ ║ ║ 
 ║ 102539 ║ 113370 ║ 82 ║ ║ ║ ║ 
 ║ 102540 ║ 113371 ║ 82 ║ ║ ║ ║ 
 ║ 102541 ║ 113372 ║ 82 ║ ║ ║ ║ 
 ║ 102542 ║ 113373 ║ 82 ║ ║ ║ ║ 
 ║ 102543 ║ 113374 ║ 82 ║ ║ ║ ║ 
 ║ 102544 ║ 113375 ║ 82 ║ ║ ║ ║ 
 ║ 102545 ║ 113376 ║ 82 ║ ║ ║ ║ 
 ║ 102546 ║ 113377 ║ 82 ║ ║ ║ ║ 
 ║ 102547 ║ 113378 ║ 82 ║ ║ ║ ║ 
 ║ 102548 ║ 113379 ║ 82 ║ ║ ║ ║ 
 ║ 102549 ║ 113380 ║ 82 ║ ║ ║ ║ 
 ║ 102550 ║ 113381 ║ 82 ║ ║ ║ ║ 
 ║ 102551 ║ 113382 ║ 82 ║ ║ ║ ║ 
 ║ 102552 ║ 113383 ║ 82 ║ ║ ║ ║ 
 ║ 102553 ║ 113384 ║ 82 ║ ║ ║ ║ 
 ╚ ════════════════════════════╩═════════════════╩═══ ══════ ═════╩═══════════╩══════════════╩══════════════╝

J'ai exécuté cette requête pour obtenir toutes les données pertinentes, je peux obtenir les données individuelles par table si cela est plus utile.

3
Tom Dee

SQL Server ne choisit jamais au hasard de supprimer ou de ne pas supprimer une ligne. Lorsque vous envoyez une instruction DELETE valide à SQL Server, elle l'exécute . Garanti. Période. S'il y a une erreur dans l'instruction, SQL Server renvoie une erreur. Voyez-vous des erreurs?

Le problème beaucoup plus probable est que la requête elle-même ne cible pas les lignes que vous pensez être, ou ces lignes n'existent pas. Affichez la structure de votre tableau, ainsi que les instructions de suppression réelles.

réponse de Robert Rodriguez ci-dessous, que j'ai surévalué lorsque la réponse a été publiée, parle de l'impact des transactions implicites, ce qui pourrait bien être la cause de votre problème. SQL Server très certainement supprime les lignes, même lorsque SET IMPLICIT_TRANSACTIONS est ON. Pour le prouver, exécutez les instructions suivantes qui créent et remplissent une table dans une fenêtre de requête dans SSMS:

USE tempdb;

DROP TABLE IF EXISTS dbo.t;
CREATE TABLE dbo.t
(
    i int NOT NULL
        CONSTRAINT t_pk
        PRIMARY KEY
        CLUSTERED
);
GO

INSERT INTO dbo.t (i)
VALUES (1);

SELECT *
FROM dbo.t;
GO

En exécutant le code ci-dessus, vous verrez cette sortie:

╔═══╗ 
 ║ i ║ 
 ╠═══╣ 
 ║ 1 ║ 
 ╚═══╝

Maintenant, dans une deuxième fenêtre de requête SSMS, exécutez le code suivant:

USE tempdb;
/*********************************
* TURN ON IMPLICIT TRANSACTIONS  *
*********************************/
SET IMPLICIT_TRANSACTIONS ON;

SELECT [State] = 'PRE DELETE'
    , [@@TRANCOUNT] = @@TRANCOUNT
    , [IMPLICIT_TRANSACTIONS] = CASE WHEN @@OPTIONS & 2 = 2 THEN 'ON' ELSE 'OFF' END
    , [Count of Rows in dbo.t] = (SELECT COUNT(1) FROM dbo.t)

DELETE 
FROM dbo.t
WHERE dbo.t.i = 1;

SELECT [State] = 'POST DELETE'
    , [@@TRANCOUNT] = @@TRANCOUNT
    , [IMPLICIT_TRANSACTIONS] = CASE WHEN @@OPTIONS & 2 = 2 THEN 'ON' ELSE 'OFF' END
    , [Count of Rows in dbo.t] = (SELECT COUNT(1) FROM dbo.t)
GO

--DISCONNECT the session after you run the above code

En exécutant le code ci-dessus, vous verrez deux ensembles de résultats:

╔════════════╦═════════════╦══════════════════════ ═╦════════════════════════╗ 
 ║ État ║ @@ TRANCOUNT ║ IMPLICIT_TRANSACTIONS ║ Nombre de lignes dans dbo.t ║ 
 ╠════════════╬═════════════╬══════════════════ ═════╬════════════════════════╣ 
 ║ PRE DELETE ║ 1 ║ ON ║ 1 ║ 
 ╚════════════╩═════════════╩═════════════════════ ══╩════════════════════════╝ 
 
 ╔═══════════ ══╦═════════════ ═══════════════════════╦════════════════════════╗ 
 ║ État ║ @@ TRANCOUNT ║ IMPLICIT_TRANSACTIONS ║ Nombre de lignes dans dbo.t ║ 
 ╠═════════════╬═════════ ════╬═══════════════════════╬═════════════════════ ═══╣ 
 ║ POST DELETE ║ 1 ║ ON ║ 0 ║ 
 ╚═════════════╩══ ═══════════╩═══════════════════════╩══════════════ ══════════╝

Dans la sortie ci-dessus, vous pouvez voir que le premier jeu de résultats indique que la ligne existe dans dbo.t. Avant le deuxième jeu de résultats, le DELETE FROM dbo.t l'instruction s'exécute. Dans le deuxième ensemble de résultats, vous pouvez voir que SQL Server a en fait supprimé la ligne. Assurez-vous maintenant de déconnecter la session ci-dessus, qui lancera un ROLLBACK TRANSACTION en raison de l'absence d'une explicite COMMIT TRANSACTION instruction, en combinaison avec SET IMPLICIT_TRANSACTIONS ON;.

Ouvrez maintenant une troisième fenêtre de requête dans SSMS et exécutez le code suivant:

SELECT [State] = 'POST ROLLBACK'
    , [@@TRANCOUNT] = @@TRANCOUNT
    , [IMPLICIT_TRANSACTIONS] = CASE WHEN @@OPTIONS & 2 = 2 THEN 'ON' ELSE 'OFF' END
    , [Count of Rows in dbo.t] = (SELECT COUNT(1) FROM dbo.t);

Vous verrez la sortie suivante indiquant que la ligne existe toujours dans dbo.t.

╔═══════════════╦═════════════╦═══════════════════ ════╦════════════════════════╗ 
 ║ État ║ @@ TRANCOUNT ║ IMPLICIT_TRANSACTIONS ║ Nombre de lignes en dbo. t ║ 
 ╠═══════════════╬═════════════╬════════════ ═══════════╬════════════════════════╣ 
 ║ POST ROLLBACK ║ 0 ║ OFF ║ 1 ║ 
 ╚═══════════════╩═════════════╩═══ ════════════════════╩════════════════════════╝

Bien sûr, la ligne existe dans le tableau, car la transaction implicite est annulée, exactement comme si vous aviez émis un ROLLBACK TRANSACTION directement après le DELETE FROM.

En supposant que vous ne pensez toujours pas que la ligne est réellement supprimée par le DELETE FROM dbo.t, permet d'ajouter la sortie de DBCC PAGE pour la table en question dans le deuxième ensemble de codes, puis réexécutez-le. Le code ci-dessous est tiré de mon article de blog sur en utilisant DBCC PAGE pour afficher les détails de la ligne

USE tempdb;
SET IMPLICIT_TRANSACTIONS ON;

SELECT [State] = 'PRE DELETE'
    , [@@TRANCOUNT] = @@TRANCOUNT
    , [IMPLICIT_TRANSACTIONS] = CASE WHEN @@OPTIONS & 2 = 2 THEN 'ON' ELSE 'OFF' END
    , [Count of Rows in dbo.t] = (SELECT COUNT(1) FROM dbo.t)


DBCC TRACEON(3604) WITH NO_INFOMSGS;
DECLARE @dbid int = DB_ID();
DECLARE @fileid int;
DECLARE @pageid int;
DECLARE cur CURSOR LOCAL FORWARD_ONLY STATIC READ_ONLY
FOR
SELECT dpa.allocated_page_file_id
    , dpa.allocated_page_page_id
FROM sys.schemas s  
    INNER JOIN sys.objects o ON o.schema_id = s.schema_id
CROSS APPLY sys.dm_db_database_page_allocations(DB_ID(), o.object_id, NULL, NULL, 'DETAILED') dpa
WHERE o.name = N't'
    AND s.name = N'dbo'
    AND dpa.page_type_desc = N'DATA_PAGE';
OPEN cur;
FETCH NEXT FROM cur INTO @fileid, @pageid;
WHILE @@FETCH_STATUS = 0
BEGIN
    DBCC PAGE (@dbid, @fileid, @pageid, 3);
    FETCH NEXT FROM cur INTO @fileid, @pageid;
END
CLOSE cur;
DEALLOCATE cur;
DBCC TRACEOFF(3604);

DELETE 
FROM dbo.t
WHERE dbo.t.i = 1;

DBCC TRACEON(3604) WITH NO_INFOMSGS;
DECLARE cur CURSOR LOCAL FORWARD_ONLY STATIC READ_ONLY
FOR
SELECT dpa.allocated_page_file_id
    , dpa.allocated_page_page_id
FROM sys.schemas s  
    INNER JOIN sys.objects o ON o.schema_id = s.schema_id
CROSS APPLY sys.dm_db_database_page_allocations(DB_ID(), o.object_id, NULL, NULL, 'DETAILED') dpa
WHERE o.name = N't'
    AND s.name = N'dbo'
    AND dpa.page_type_desc = N'DATA_PAGE';
OPEN cur;
FETCH NEXT FROM cur INTO @fileid, @pageid;
WHILE @@FETCH_STATUS = 0
BEGIN
    DBCC PAGE (@dbid, @fileid, @pageid, 3);
    FETCH NEXT FROM cur INTO @fileid, @pageid;
END
CLOSE cur;
DEALLOCATE cur;
DBCC TRACEOFF(3604);

SELECT [State] = 'POST DELETE'
    , [@@TRANCOUNT] = @@TRANCOUNT
    , [IMPLICIT_TRANSACTIONS] = CASE WHEN @@OPTIONS & 2 = 2 THEN 'ON' ELSE 'OFF' END
    , [Count of Rows in dbo.t] = (SELECT COUNT(1) FROM dbo.t)
GO

Les ensembles de résultats sont exactement les mêmes que dans le deuxième ensemble ci-dessus, cependant si nous regardons l'onglet "Messages", nous verrons la sortie de l'exécution de DBCC PAGE avant et après la suppression. La sortie de la première PAGE DBCC avant la suppression, et avec la section d'en-tête supprimée, ressemble à:

. 
. 
. 
 Slot 0 Offset 0x6b Longueur 11 
 
 Type d'enregistrement = PRIMARY_RECORD Attributs d'enregistrement = NULL_BITMAP Taille d'enregistrement = 11 
 
 Vidage de la mémoire @ 0x000000B76B27A06B 
 
 0000000000000000: 10000800 01000000 010000 ........... 
 
 Emplacement 0 Colonne 1 Décalage 0x4 Longueur 4 Longueur (physique) 4 
 
 I = 1 
 
 Emplacement 0 Décalage 0x0 Longueur 0 Longueur (physique) 0 
 
 KeyHashValue = (8194443284a0) 

Vous pouvez voir dans la sortie ci-dessus le Record Type est PRIMARY_RECORD et Slot 0 contient la valeur: 1. Cela indique que la ligne existe dans le tableau et a la valeur attendue.

La sortie de DBCC PAGE, post delete, avec la même section d'en-tête supprimée, ressemble à:

Emplacement 0 Décalage 0x6b Longueur 11 
 
 Type d'enregistrement = GHOST_DATA_RECORD Attributs d'enregistrement = NULL_BITMAP Taille d'enregistrement = 11 
 
 Vidage de la mémoire @ 0x000000B76B27A06B 
 
 0000000000000000: 1c000800 01000000 010000 ........... 
 
 Emplacement 0 Colonne 1 Décalage 0x4 Longueur 4 Longueur (physique) 4 
 
 i = 1 
 
 Emplacement 0 Décalage 0x0 Longueur 0 Longueur (physique) 0 
 
 KeyHashValue = (8194443284a0) 

La sortie montre clairement que le Record Type pour l'emplacement 0 est GHOST_RECORD, qui indique que l'emplacement (ligne) a été supprimé .

Nous pouvons également consulter le journal des transactions via la fonction système non documentée, sys.fn_dblog, en utilisant le m_lsn valeur de la sortie DBCC PAGE:

SELECT *
FROM sys.fn_dblog('102:281659:60', '102:281659:60');

La sortie montre que la suppression a été enregistrée dans le journal des transactions:

╔════════════════════════╦═════════════════╦══════ ═════════════╦════════════════╗ 
 ║ LSN actuel ║ Opération ║ Contexte ║ ID de transaction ║ 
 ╠════════════════════════╬═════════════════╬════ ═══════════════╬════════════════╣ 
 ║ 00000066: 00044c3b: 003c ║ LOP_DELETE_ROWS ║ LCX_MARK_AS_GHOST ║ 0000 : 00160373 ║ 
 ╚════════════════════════╩════════════════ ═╩═══════════════════╩════════════════╝

Je n'ai que les premières colonnes de la sortie affichées ici par souci de concision, mais vous pouvez clairement voir le LOP_DELETE_ROWS opération, avec un contexte de LCX_MARK_AS_GHOST, qui indique que SQL Server a enregistré l'opération DELETE dans le journal des transactions.

12
Max Vernon

Il est possible que le code de service Web soit activé transactions implicites .

Pour illustrer comment cela fonctionne, j'ai créé une table simple dans tempdb, inséré une ligne, activé les transactions implicites et supprimé la ligne:

USE tempdb;

CREATE TABLE dbo.t
(
    someval int
);

INSERT INTO dbo.t (someval)
VALUES (1);

SET IMPLICIT_TRANSACTIONS ON;
DELETE FROM dbo.t;

Si je déconnecte et reconnecte maintenant la session, exécutez ce code:

USE tempdb;
SELECT *
FROM dbo.t

Je vois que la rangée est toujours dans le tableau. Cela correspond assez bien à la description de ce que vous voyez. Vous devrez vérifier le code du service Web pour cela, ou regarder une trace pour voir si cette option est en vigueur.

Si vous êtes intéressé, vous pouvez voter cet élément Not Connect pour demander à Microsoft de fournir une méthode plus simple pour déterminer s'il est implicite les transactions sont activées.

8
Robert Rodriguez

Avez-vous vérifié si vous obtenez des blocages?

Mon expérience est dans Sybase mais SQL Server devrait être assez similaire. Le gestionnaire de messages client (ce serait dans le service Web) obtient le message # 1205 pour un blocage. En outre, le blocage doit être enregistré dans le journal SQL Server.

Deux sessions devraient se disputer pour deux verrous pour provoquer l'impasse. Je pouvais voir un service Web utilisant un pool de connexions, résultant en plusieurs sessions. La lutte pour deux verrous est assez facile à réaliser. Un verrou peut se trouver sur une page de données et un autre sur une page d'index.

4
Jay Anderson