Voici le problème que je vais traiter : je veux pouvoir m'assurer dans une
transaction qu'un jeu d'enregistrements ne sera pas touché pendant que j'opère
une série de modifications. Par exemple, je ne veux pas qu'on puisse modifier
une série d'adresses pendant que je mets à jour le contact qui les
possède. Pour être plus concret, je veux bloquer les adresses 31 et 52
pendant que je modifie le contact John Smith. Par exemple :
BEGIN TRAN
SELECT * INTO #TempTable FROM Person.Address Where AddressID in
(31,52)
UPDATE Person.Person Set Title = 'Lord' Where LastName = 'Smith' and FirstName
= 'John'
UPDATE Person.PersonPhone Set [...]
Par défaut, le niveau d'isolation de transaction de SQL Server est
READ COMMITED. En d'autres termes, cela signifie que l'on ne
peut lire ou modifier que des données qui ont validées (comitées pour
reprendre le barbarisme franglais). Selon le moteur de transactions, les
adresses peuvent être modifiées pendant que ma transaction a lieu.
Techniquement, dans le moteur SQL, cela se traduit par des
locks partagés (Shared) sur la table
Address qui durent le temps du premier SELECT et des locks
exclusifs (eXclusive) sur les tables modifiées. Une
fois le SELECT passé, les locks Shared sont libérés, laissant la
place pour des modifications. En revanche les locks exclusifs sont
maintenus jusqu'au COMMIT (ou ROLLBACK), empêchant les modifications. On
peut le vérifier en regardant les verrous sur les tables en question :
SET TRANSACTION ISOLATION LEVEL READ
COMMITTED --mode par défaut
BEGIN TRAN
SELECT *
FROM Person.Address
WHERE AddressID IN (31,52)
select resource_type, resource_description, request_mode, request_type
from sys.dm_tran_locks
ROLLBACK

Lors de la consultation des locks, il n'y en a aucun donc une instruction
parallèle peut modifier les adresses. Nous allons donc utiliser un autre niveau
de transaction pour changer le comportement du moteur.
En choisissant le niveau juste au dessus, REPEATABLE READ,
le moteur va poser des locks Shared et les maintenir le temps de la
transaction. C'est ce que l'on appelle des lectures répétables. Pour
modifier un enregistrement, le moteur doit poser un lock exclusif et donc
doit attendre de n'avoir plus de locks Shared. Voyons le comportement des
verrous, pour cela, il suffit de changer le niveau d'isolation dans la première
ligne :
SET TRANSACTION ISOLATION LEVEL
REPEATABLE READ
BEGIN TRAN
SELECT *
FROM Person.Address
WHERE AddressID IN (31,52)
select resource_type, resource_description, request_mode, request_type
from sys.dm_tran_locks
ROLLBACK

On voit bien que des locks Shared (S et IS) sont maintenus
après le SELECT. Ceci garantie que ces enregistrements ne seront pas modifiés
le temps de ma transaction. De plus, du fait que ce soit des locks Shared,
une autre transaction peut lire ces enregistrements.
Bien évidemment, la gestion des locks est très fine dans SQL Server et se
complexifie avec l'ensemble des requêtes à un instant T. SQL Server va tenter
de verrouiller le niveau le plus fin et le moins contraignant pour respecter
vos niveaux d'isolation et garantir la transactionalité. Et tout ceci se
combine avec les différents objets liés à la table (index, allocations,
etc.).
Vous aurez noté au passage l'utilisation de la vue système
sys.dm_tran_locks qui remplace depuis 2005 l'ancien
sp_lock et qui vous aidera à comprendre le fonctionnement du
moteur transactionnel et à minimiser la contention sur vos bases.
Bonne optimisation...