2010-02-26 3 views
1

Je suis récemment tombé sur une situation plutôt frustrante où SQL Server refuse d'émettre des verrous uniquement contre une clé primaire lorsqu'une instruction comme celle-ci select * from table with (rowlock updlock) where key=value est exécutée à son encontre. Maintenant, ne vous méprenez pas ici, ça bloque la ligne mais ça va un peu plus loin et verrouille la table aussi. J'ai lu à propos de l'escalade de verrouillage SQL et j'ai regardé l'utilisation d'index spécifiques dans les indicateurs de verrouillage, mais vous voyez, ce n'est pas pratique quand il y a de nombreux index dans une table avec des millions d'enregistrements et mises à jour qui doivent se produire sur ces enregistrements. Pour une petite table et une requête spécifique, il est possible d'obtenir le comportement désiré, mais lorsque la table a une grande largeur (plusieurs colonnes) et que de nombreux processus utilisent les données, cette méthode disparaît et peut devenir un véritable point de désaccord.Quels changements devraient être apportés à l'architecture de verrouillage de SQL Server pour le rendre plus convivial pour les développeurs?

Ce que je voudrais voir ajouter est une nouvelle lockhint sucent comme PKLock (qui se PRIMAIRES Key Lock) qui délivre un verrou contre la clé primaire d'une ligne et à tout moment un index, une scan de table ou autre méthode est utilisée pour obtenir la ligne, il vérifie ce verrou et l'honore au lieu de verrouiller la table entière.

En tant que tel verrou de table aurait pas besoin d'être émis et cela augmenterait considérablement la capacité d'exécution en parallèle du code par rapport à la DB.

S'il vous plaît peser sur cette idée et signaler les défauts qu'il pourrait avoir, les moyens qu'il pourrait être amélioré ou d'autres éléments qui devraient être ajoutés pour résoudre mon dilemme.

EDIT

@Remus

Si j'exécute cette requête

begin transaction 
select lockname from locks where lockname='A' 
update Locks Set locked=1 where lockname='A' 

puis cette requête:

begin transaction 
select lockname from locks where lockname='A' 

ligne A est retourné dans les deux exemples avant de commettre les transactions. C'est lire derrière la mise à jour, pas de blocage.

Une solution réussie devrait faire ce qui suit sans spécifier les index à utiliser:

  1. avec Query 1: Lire et verrouiller A, mettre à jour un
  2. Avec Requête 2: Lire et verrouiller B, mise à jour B, Commit requête 2
  3. avec Query 2: Lire B et être bloqué jusqu'à son blocage sur A sont libérés
  4. avec Query 1: Engagez requête 1
  5. Avec requête 2: Lire et verrouiller A, mettre à jour A, Engagez requête 2
+0

Ne devriez-vous pas suggérer cela à Microsoft au lieu d'ici? –

+0

Je pense que Microsoft prendrait mieux note de cela s'il venait de la communauté. D'ailleurs c'est peut-être une mauvaise idée et si oui, j'aimerais savoir pourquoi et ce qui pourrait être fait différemment pour résoudre ce problème. – Middletone

+0

donc votre déclaration exacte: 'select * de la table avec (rowlock updlock) où key = value' verrouille toute la grande table? ou votre requête est-elle plus complexe et êtes-vous sûr que la table entière est verrouillée? –

Répondre

3

Vous avez posé cette question et a donné la réponse: fix your schema and your code. Dans cet article, le conflit de verrous était un verrou IX, et un conflit sur les verrous d'intention indique des verrous à haute granularité, qui à leur tour indiquent des scans de table.Vous n'avez pas besoin d'indices de verrouillage, vous avez juste besoin d'un index et de requêtes correctes. Prenez par exemple votre autre question, Why does row level locking not appear to work correctly in SQL server? où réponse est trivial: Verrous de table doit être organisé par un index ordonné en clusters sur nom de verrouillage:

CREATE TABLE [dbo].[Locks]( 
    [LockName] [varchar](50) NOT NULL, 
    [Locked] [bit] NOT NULL, 
    CONSTRAINT [PK_Locks] PRIMARY KEY CLUSTERED ([LockName])); 
GO  

insert into Locks (LockName, Locked) values ('A', 0); 
insert into Locks (LockName, Locked) values ('B', 0); 
GO 

Sur une session faire:

begin transaction 
update Locks 
    set Locked=1 
    output inserted.* 
    where LockName = 'A'; 

autre séance faire:

begin transaction 
update Locks 
    set Locked=1 
    output inserted.* 
    where LockName = 'B'; 

Il n'y a pas de conflit de mise à jour, pas de blocage, pas besoin de conseils (de mauvais), rien. Juste bon ole 'schéma correct et la conception de la requête.

En note, le verrou que vous décrivez ici existe déjà et s'appelle key-locks. Ils sont le mode implicite implicite SQL Server fonctionne. Juste comment dans le monde imaginez-vous que SQL Server peut publier des numéros de référence TPC-C de transaction de 16000 tpc par seconde? Vous avez toute la capacité de parallélisme dont vous avez besoin sur le serveur, il vous suffit de lire un livre ou deux pour comprendre comment l'utiliser. Il y a beaucoup de littérature sur le sujet, vous pouvez commencer par Transaction Processing: Concepts and Techniques.

Mise à jour

begin transaction 
select lockname from locks where lockname='A' 
update Locks Set locked=1 where lockname='A' 

Cela ne marchera jamais, peu importe combien de/divers conseils de verrouillage vous essayez. C'est pourquoi vous avez la mise à jour avec la syntaxe de sortie:

begin transaction 
update Locks 
Set locked=1 
output inserted.* 
where lockname='A' 

cela vous assure que vous mettez d'abord à jour, puis renvoyez ce que vous avez mis à jour. Cette technique est assez courante dans les bases de données pour exactement la sémantique que vous recherchez: l'acquisition de ressources. En fait, cette technique est la pierre angulaire de l'enfant de l'affiche d'acquisition de ressources: le traitement de la file d'attente. Voir Queues paragraphe dans OUTPUT Clause. Dans les files d'attente, vous avez une table des ressources à traiter, et chaque fil attrape un, les serrures et commencer le traitement:

create table Resources (
    id int identity(1,1) not null, 
    enqueue_time datetime not null default getutcdate(), 
    is_processing bit not null default 0, 
    payload xml); 

create clustered index cdxResources on Resources 
    (is_processing, enqueue_time); 
go 

-- enqueue: 
insert into Resources (payload) values ('<do>This</do>'); 
insert into Resources (payload) values ('<do>That</do>'); 
insert into Resources (payload) values ('<do>Something</do>'); 
insert into Resources (payload) values ('<do>Anything</do>'); 

maintenant des sessions séparées, exécutez:

--dequeue 
begin transaction; 
with cte as (
    select top(1) * 
    from Resources with(readpast) 
    where is_processing = 0 
    order by enqueue_time) 
update cte 
    set is_processing = 1 
    output inserted.*; 

Vous verrez chaque session saisit sa propre ressource, la verrouille et saute tout ce qui est verrouillé par tout le monde. Il se trouve que j'ai en production un système qui fonctionne exactement comme cela, avec plus de 5M de ressources dans la table (ce sont des requêtes de traitement de paiements web), et environ 50 par seconde de processeurs concurents (environ 2sec). par appel à traiter). Sur un morceau de matériel indésirable. Donc c'est absolument possible.

+0

Alors que ma table de verrous effectuait des analyses de table à l'origine, j'ai ajouté un index au champ lockname pour résoudre ce problème (et inclure l'index dans la requête). J'ai d'autres tables qui ne font pas d'analyses de table qui ont 700 000 enregistrements ou plus. Dans ces cas, il n'y a pas d'analyse de table et le verrouillage est très important. Votre exemple ci-dessus ne parvient pas à ajouter une instruction select (qui était dans l'exemple original) qui, lorsque vous l'exécutez dans les transactions vous montrera que la requête 2 peut faire une lecture répétable sur les données de la requête 1 avant qu'elle ne soit validée. – Middletone

+0

Ajoutez 'select * à partir des verrous où lockname = 'A'' avant vos deux requêtes de mise à jour et vous constaterez que vous pouvez lire les données quand vous ne devriez pas pouvoir. Tout l'intérêt de mes messages est que je ne veux pas être capable de lire les données tant qu'elles ne sont pas validées ET cela ne devrait pas m'empêcher de lire des lignes non-intégrées qui ne sont pas incluses dans cette lecture. – Middletone

+0

'sortie insérée. *' ** est la sélection **. En outre, 'select from locks où lockname = 'A'' fait exactement ce qu'il est censé faire: des blocs derrière la mise à jour. J'ai déjà vu des gens insensibles aux conseils, mais vous mettez la barre très haut ... –

Questions connexes