web-dev-qa-db-fra.com

SQL Server - Sélectionner pendant que l'insertion sur une autre transaction donne des résultats inattendus

J'ai trébuché à la situation qui change mes connaissances sur les transactions et de verrouiller fondamentalement (je ne sais pas grand chose si) et j'ai besoin d'aide pour le comprendre.

Disons que j'ai une table comme ceci:

CREATE TABLE [dbo].[SomeTable](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[SomeData] [varchar](200) NOT NULL,
[Moment] [datetime] NOT NULL,
[SomeInt] [bigint] NOT NULL
) ON [PRIMARY]

et j'exécute cette "insérer 1000 lignes dans une transaction" requête:

BEGIN TRAN t1

DECLARE @i INT = 0

WHILE @i < 1000
BEGIN
    SET @i = @i + 1

    INSERT INTO [SomeTable] ([SomeData] ,Moment, SomeInt)
    VALUES (CONVERT(VARCHAR(255), NEWID()), getdate(), @i)

    WAITFOR DELAY '00:00:00:010'
END

COMMIT TRAN t1

Bien que cette transaction fonctionne, j'exécute une sélection simple:

SELECT Id, Moment, SomeData, SomeInt FROM [SomeTable]

Il n'est pas toujours possible de le reproduire (apparemment dépend des horaires), mais parfois de sélectionner une requête, une fois les finitions de transaction insérées, renvoie moins de 1000 rangées. Dans mon ignorance, j'ai cru que Select retournera toujours 1000 rangées (étant donné que le niveau d'isolement est lu engagé), mais j'ai évidemment mal compris la manière dont les transactions et les travaux de verrouillage.

Cependant, si je mettais une clé principale sur la colonne ID (qui génère un indice en cluster), sélectionnez la requête, aussi longtemps que j'ai essayé, retournez toutes les 1000 lignes. Mettre des index Quelques autres voies, avec une index en cluster sur la clé composite et un indice non clusterné sur certaines autres colonnes, peut à nouveau entraîner une rédaction de nombre moins de lignes que prévu.

Donc, j'ai ces questions:

  1. Pourquoi choisir ne renvoie pas toujours toutes les lignes commises par transaction?
  2. Si cela est attendu Comportement Quel est le meilleur moyen de le faire fonctionner comme je suis attendu? Fondamentalement, je veux que vous puissiez choisir de retourner l'état du tableau après (ou avant) la transaction, pas une donnée à moitié terminée. L'isolement d'instantané n'est actuellement pas une option. Mettre le matal semble faire le travail, mais y a-t-il une meilleure solution? Dans la vie réelle, j'ai des tables que je ne voudrais pas verrouiller ce niveau s'il n'est pas absolument nécessaire.
  3. Pourquoi mettre un indice change ce comportement?

Merci d'avance.

8
poke

Je n'ai pas réussi à reproduire cela après avoir exécuté votre code plusieurs fois.

Je présume que cela doit arriver quand une ligne ultérieure est insérée sur une page antérieure dans le fichier.

Donc l'ordre des opérations est (par exemple)

  • Rangées insérées dans le tas aux pages 200, 207, 223
  • Sélectionnez l'instruction Démarrage et effectue une analyse commandée d'une allocation. Trouve que la première page est 200 et est bloquée en attente d'une location à la ligne à relâcher.
  • D'autres lignes sont insérées par la première transaction. Certains d'entre eux sont alloués sur une page avant 200. Insérer une transaction commises.
  • LOCK LOCK publié et poursuit l'allocation commandée numérisé. Les lignes plus tôt dans le fichier sont manquées.

Le tableau comprenait 10 pages. Par défaut, les 8 premières pages seront attribuées à partir d'extensions mixtes, puis il sera attribué une étendue uniforme. Peut-être que dans votre espace de cas était disponible dans le dossier d'une étendue uniforme gratuit avant les extensions mixtes utilisées.

Vous pouvez tester cette théorie en exécutant ce qui suit dans une fenêtre différente après avoir reproduit le problème et en voyant si les lignes manquantes de l'original SELECT apparaissent tous au début de ces Resultset.

SELECT [SomeData],
       Moment,
       SomeInt,
       file_id,
       page_id,
       slot_id
FROM   [SomeTable] 
/*Undocumented - Use at own risk*/
CROSS APPLY sys.fn_PhysLocCracker(%% physloc %%)
ORDER BY page_id, SomeInt

L'opération contre une table indexée sera dans l'ordre des essences d'index plutôt que dans l'ordre d'attribution afin que ce scénario ne soit pas affecté par ce scénario.

Un scan commandé d'une allocation peut être effectué contre un index, mais il est seulement pris en compte si la table est suffisamment grande et que le niveau d'isolement est lu non engagé ou une serrure de table est maintenue.

Parce que la lecture commune libère généralement des verrômes dès que les données sont lues, il est possible pour une analyse par rapport à l'index de lecture de lignes deux fois ou non du tout (si la clé d'index est mise à jour par une transaction simultanée provoquant l'avant ou l'arrière. ) Voir LE NIVEAU D'ISOLATION EN LIRE Pour plus de discussion sur ce type de problème.


En passant, j'avais initialement envisagé l'affaire indexée que l'indice était sur l'une des colonnes qui augmente par rapport à l'ordre d'insertion (n'importe lequel de l'ID, du moment, de la peinture). Cependant, même si l'indice en cluster est sur le hasard SomeData, le problème ne survient toujours pas.

J'ai essayé

DBCC TRACEON(3604, 1200, -1) /*Caution. Global trace flag. Outputs lock info
                               on every connection*/

SELECT TOP 2 *,
             %%LOCKRES%%
FROM   [SomeTable] WITH(nolock)
ORDER BY [SomeData];

SELECT *,
       %%LOCKRES%%
FROM   [SomeTable]
ORDER BY [SomeData];

/*Turn off trace flags. Doesn't check whether or not they were on already 
  before we started, with TRACEOFF*/
DBCC TRACEOFF(3604, 1200, -1)

Les résultats étaient comme ci-dessous

enter image description here

Le deuxième Resultins comprend les 1 000 lignes. Les informations de verrouillage montrent que même s'il a été bloqué en attente de la ressource de verrouillage 24c910701749 Lorsque le verrou a été publié ne le fait pas continuez simplement le scan de ce point. Au lieu de cela, il libère immédiatement cette serrure et acquiert une serrure de ligne sur la nouvelle première rangée.

enter image description here

12
Martin Smith