0

Notre application nécessite un mécanisme de planification simple - nous pouvons programmer une seule visite par pièce pour le même intervalle de temps (mais une visite peut utiliser une ou plusieurs pièces). Utilisation de SQL Server 2005, la procédure d'échantillonnage pourrait ressembler à ceci:Problèmes de simultanéité avec l'application de planification

CREATE PROCEDURE CreateVisit 
    @start datetime, @end datetime, @roomID int 
AS 
BEGIN 
DECLARE @isFreeRoom INT 

BEGIN TRANSACTION 

SELECT @isFreeRoom = COUNT(*) 
FROM visits V 
INNER JOIN visits_rooms VR on VR.VisitID = V.ID 
WHERE @start = start AND @end = [end] AND VR.RoomID = @roomID 

IF (@isFreeRoom = 0) 
BEGIN 
    INSERT INTO visits (start, [end]) VALUES (@start, @end) 
    INSERT INTO visits_rooms (visitID, roomID) VALUES (SCOPE_IDENTITY(), @roomID) 
END 

COMMIT TRANSACTION 
END 

Afin de ne pas avoir en même temps la même salle prévue pour deux visites, comment devrions-nous traiter ce problème dans la procédure? Devrions-nous utiliser le niveau d'isolation de transaction SERIALIZABLE ou peut-être utiliser des indicateurs de table (verrous)? Quel est le meilleur?

+2

Pas une réponse à votre question, mais vous devez utiliser SCOPE_IDENTITY pas @@ IDENTITY –

Répondre

1

Je voudrais que l'application appelante passe dans une liste d'ID de pièce séparés par des virgules et les scinde dans le SQL, en insérant toutes les lignes avec un INSERT. Pour ce faire, avec les conseils de verrouillage appropriés (sur un seul SELECT), devrait permettre à votre procédure de programmation de fonctionner.

I prefer the number table approach to split a string in TSQL, mais vous pouvez utiliser votre propre méthode split si vous en avez une. Voici comment faire le travail partagé numéro de table d'approche:

Pour que cette méthode fonctionne, vous devez faire une configuration horaire:

SELECT TOP 10000 IDENTITY(int,1,1) AS Number 
    INTO Numbers 
    FROM sys.objects s1 
    CROSS JOIN sys.objects s2 
ALTER TABLE Numbers ADD CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number) 

Une fois la table des numéros est mis en place, créer cette scission fonction:

CREATE FUNCTION [dbo].[FN_ListToTable] 
(
    @SplitOn char(1)  --REQUIRED, the character to split the @List string on 
    ,@List  varchar(8000)--REQUIRED, the list to split apart 
) 
RETURNS TABLE 
AS 
RETURN 
(

    ---------------- 
    --SINGLE QUERY-- --this will not return empty rows 
    ---------------- 
    SELECT 
     ListValue 
     FROM (SELECT 
        LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(@SplitOn, List2, number+1)-number - 1))) AS ListValue 
        FROM (
          SELECT @SplitOn + @List + @SplitOn AS List2 
         ) AS dt 
         INNER JOIN Numbers n ON n.Number < LEN(dt.List2) 
        WHERE SUBSTRING(List2, number, 1) = @SplitOn 
      ) dt2 
     WHERE ListValue IS NOT NULL AND ListValue!='' 

); 
GO 

Vous pouvez maintenant facilement diviser une chaîne CSV dans une table et se joindre à elle:

select * from dbo.FN_ListToTable(',','1,2,3,,,4,5,6777,,,') 

SORTIE:

ListValue 
----------------------- 
1 
2 
3 
4 
5 
6777 

(6 row(s) affected) 

C'est ce que je puis faire votre procédure:

CREATE PROCEDURE CreateVisit 
    @start datetime, @end datetime, @roomIDs varchar(8000) 
AS 
BEGIN 
DECLARE @RowID INT 

BEGIN TRANSACTION 

IF NOT EXISTS (SELECT 
        1 
        FROM visits_rooms   (HOLDLOCK,UPDLOCK) v 
         INNER JOIN dbo.FN_ListToTable(',',@roomIDs) r ON v.RoomID=r.ListValue 
        WHERE @start = start AND @end = [end] AND VR.RoomID = @roomID --copy of your logic, but shouldn't it be WHERE start>[email protected] AND [end]<[email protected] 
      ) 
BEGIN 
    INSERT INTO visits (start, [end]) VALUES (@start, @end) 
    SELECT @RowID=SCOPE_IDENTITY() 
    INSERT INTO visits_rooms 
      (visitID, roomID) 
     SELECT 
      @RowID, r.ListValue 
      FROM dbo.FN_ListToTable(',',@roomIDs) r 

END 

COMMIT TRANSACTION 
END 

GO

si vous avez beaucoup RoomIDs un calendrier un essai, vous pouvez les diviser en un @TempTable variable ou une table réelle #TempTable d'abord, puis le réutiliser dans le IF EXISTS et INSERT SELECT.

0

Je l'ai fait dans le passé ...

...

BEGIN TRANSACTION 

SELECT @isFreeRoom = COUNT(*) 
FROM visits V WITH (HOLDLOCK, ROWLOCK) 

... Le verrou sortira à la fin de la transaction.

0

WHERE @start = start AND @end = [end] AND VR.RoomID = @roomID

Cette vérification est incorrecte parce que vous ne trouverez si la salle est prévue exactement entre @Start et @end. Vous trouverez la pièce "libre" si elle est prévue entre (@ start-1, @end) ou (@start, @ end + 1), en d'autres termes tout intervalle de chevauchement qui ne correspond pas exactement au début et fin. Une vérification correcte est comme ceci:

WHERE [start] < @end AND [end] > @start AND RoomID = @roomID 

En ce qui concerne la programmation, le plus simple est de faire comme vous le faites dans une transaction sérialisable. C'est simple, mais loin d'être idéal: vous allez toucher des deadlocks et vous aurez des problèmes d'évolutivité. Mais avec une charge légère, cela fonctionnera plutôt bien.Mais dans la plupart des systèmes de planification, l'allocation des ressources (sièges, salles, etc.) affiche d'abord la ressource sélectionnée à l'utilisateur, puis l'alloue. C'est parce que les humains sont pointilleux et exigeants et ils peuvent vouloir ajuster la ressource allouée (avoir la préférence de pièce, besoin des salles adjacentes etc.). Si vous voulez un système hautement extensible et sans interblocage qui peut automatiser l'allocation des ressources, vous devez utiliser une table de ressources pour les pièces avec une ligne pour chaque pièce et l'heure de visite. Autrement dit, si vos visites durent une heure et que le programme dure 12 heures par jour, vous disposez de 12 lignes par chambre et par jour. À partir de ce tableau, vous planifiez les chambres en un seul passage, avec une haute efficacité:

UPDATE TOP(@numberOfRooms) RoomHours WITH (ROWLOCK, READPAST) 
SET Free = 0 
OUTPUT DELETED.RoomID 
WHERE Free = 1 
AND RoomHour BETWEEN @start AND @end; 
Questions connexes