Mad cursor (SQL Server) 使程序卡死和崩溃

Mad cursor (SQL Server) make the program freeze and crash

我开发了一个航班预订程序。我在触发器中有一个(荒谬的)条件,如果它被填充则应该执行。

我的问题是,当我调用我的存储过程为客户预订航班时,我的程序由于延迟而冻结和崩溃。

我知道这个问题出在我的触发器上,你知道卡住的是什么吗?

如果您需要更多详细信息(表、存储过程、代码),请随时告诉我! (对法语评论感到抱歉:p)

ALTER TRIGGER [dbo].[Tr_Check60j]
ON [dbo].[Reservation]
FOR INSERT
AS
BEGIN
    DECLARE @IdVolInsere INT, @DateVol DATETIME, @IdVolExistant INT, @IdClient INT

    SELECT @IdVolInsere = v.VOL_Id 
    FROM Vol v
    JOIN Planning AS p ON p.PLA_Vol_Id = v.VOL_Id
    JOIN inserted AS i ON p.PLA_Id = i.RES_Pla_Id
    WHERE v.VOL_Id = p.PLA_Vol_Id
      AND p.PLA_Id = i.RES_Pla_Id

    SELECT @IdClient = i.RES_Client_Id
    FROM inserted i

    DECLARE @DateVolExistant DATETIME, @DateVolInsere DATETIME;
    DECLARE @IdVilleDepartExistant INT, @IdVilleArriveeExistant INT;
    DECLARE @IdVilleDepartInseree INT, @IdVilleArriveeInseree INT;

        -- Sélectionne l'id des villes du vol inséré
    SELECT @IdVilleDepartInseree = v.VOL_Vil_Depart_Id FROM Vol v WHERE v.VOL_Id = @IdVolInsere
    SELECT @IdVilleArriveeInseree = v.VOL_Vil_Arrivee_Id FROM Vol v WHERE v.VOL_Id = @IdVolInsere

    SELECT @DateVolInsere = p.PLA_Date
    FROM Planning p
    JOIN inserted AS i ON i.RES_Pla_Id = p.PLA_Id
    WHERE i.RES_Pla_Id = p.PLA_Id

    -- Curseur qui compare chaque vol du client existant avec le vol inséré pour vérifier si le vol existant
    -- est un vol retour, si oui, les 2 vols ont-ils plus de 60 jours entre eux? 
    -- Si oui, alors il faut vérifier s'il existe un vol réservé entre ces 2 vols --> S'il y en a un: ERREUR
    DECLARE CR_Check_Vols_Par_Id_Client CURSOR FOR 
        SELECT v.VOL_Id
        FROM Vol v
        JOIN Planning AS p ON v.VOL_Id = p.PLA_Vol_Id
        JOIN Reservation AS r ON p.PLA_Id = r.RES_Pla_Id
        WHERE r.RES_Client_Id = @IdClient

    OPEN CR_Check_Vols_Par_Id_Client

    FETCH CR_Check_Vols_Par_Id_Client INTO @IdVolExistant

    WHILE @@FETCH_STATUS = 0
    BEGIN
        -- Sélectionne l'id des villes aller (mêmes villes) du dernier vol existant à moins de 60j
        SELECT @IdVilleDepartExistant = v.VOL_Vil_Depart_Id FROM Vol v WHERE v.VOL_Id = @IdVolExistant
        SELECT @IdVilleArriveeExistant = v.VOL_Vil_Arrivee_Id FROM Vol v WHERE v.VOL_Id = @IdVolExistant

            -- Vérifie s'il existe un vol aller pour le vol inséré
        IF (@IdVilleDepartInseree = @IdVilleArriveeExistant) AND (@IdVilleArriveeInseree = @IdVilleDepartExistant)
        BEGIN

            SELECT @DateVolExistant = p.PLA_Date
            FROM Planning p
            JOIN Reservation AS r
            ON r.RES_Pla_Id = p.PLA_Id
            JOIN Vol AS v
            ON p.PLA_Vol_Id = @IdVolExistant
            JOIN inserted AS i
            ON r.RES_Id = i.RES_Id
            WHERE r.RES_Client_Id = @IdClient

            -- Vérifie si le vol inséré est à une date de moins de 60 jours du vol retour existant
            IF ( (DATEDIFF(DAY, @DateVolExistant , @DateVolInsere)) < 60  )
            BEGIN

            DECLARE @CheckVolEntre INT
                -- Sélectionne un vol existant entre le vol aller inséré et le vol retour existant
                SELECT @CheckVolEntre = v.VOL_Id
                FROM Vol v
                JOIN Planning AS p
                ON v.VOL_Id = p.PLA_Vol_Id
                JOIN Reservation AS r
                ON p.PLA_Id = r.RES_Pla_Id
                WHERE r.RES_Client_Id = @IdClient AND v.VOL_Id BETWEEN @IdVolExistant AND @IdVolInsere
                -- Vérifie s'il existe un vol existant entre le vol aller inséré et le vol retour existant
                    IF (@CheckVolEntre != NULL)
                    BEGIN
                        RAISERROR('Réservation impossible', 1, 601)
                        ROLLBACK TRANSACTION
                        STOP
                END
            END
        END
    END
    CLOSE CR_Check_Vols_Par_Id_Client
    DEALLOCATE CR_Check_Vols_Par_Id_Client
END

提前致谢!

乔恩

这只是一种临时解决方法。如我的评论所述,您需要从触发器中移除光标。

您可能 运行 遇到锁定问题。尝试下面的解决方法,看看是否有帮助。我还解决了一些编码问题。如前一条评论所述,您不能使用 != 检查 NOT NULL,它必须是 IS NOT NULLIS NULL.

ALTER TRIGGER [dbo].[Tr_Check60j]
ON [dbo].[Reservation]
FOR INSERT
AS
BEGIN

    DECLARE @IdVolInsere INT, @DateVol DATETIME, @IdVolExistant INT, @IdClient INT

    SELECT 
        @IdVolInsere = v.VOL_Id 
    FROM Vol v WITH ( NOLOCK )
    JOIN Planning AS p WITH ( NOLOCK )
        ON p.PLA_Vol_Id = v.VOL_Id
    JOIN inserted AS i 
        ON p.PLA_Id = i.RES_Pla_Id
    WHERE 
        v.VOL_Id = p.PLA_Vol_Id
        AND p.PLA_Id = i.RES_Pla_Id

    SELECT @IdClient = i.RES_Client_Id FROM inserted i;

    DECLARE @DateVolExistant DATETIME, @DateVolInsere DATETIME;
    DECLARE @IdVilleDepartExistant INT, @IdVilleArriveeExistant INT;
    DECLARE @IdVilleDepartInseree INT, @IdVilleArriveeInseree INT;

        -- Sélectionne l'id des villes du vol inséré
    SELECT 
        @IdVilleDepartInseree = v.VOL_Vil_Depart_Id 
        , @IdVilleArriveeInseree = v.VOL_Vil_Arrivee_Id
    FROM Vol v WITH ( NOLOCK )
    WHERE 
        v.VOL_Id = @IdVolInsere;

    SELECT 
        @DateVolInsere = p.PLA_Date
    FROM Planning p WITH ( NOLOCK )
    JOIN inserted AS i 
        ON i.RES_Pla_Id = p.PLA_Id
    WHERE 
        i.RES_Pla_Id = p.PLA_Id

    -- Curseur qui compare chaque vol du client existant avec le vol inséré pour vérifier si le vol existant
    -- est un vol retour, si oui, les 2 vols ont-ils plus de 60 jours entre eux? 
    -- Si oui, alors il faut vérifier s'il existe un vol réservé entre ces 2 vols --> S'il y en a un: ERREUR
    DECLARE CR_Check_Vols_Par_Id_Client CURSOR FOR 
    SELECT 
        v.VOL_Id 
    FROM Vol v WITH ( NOLOCK )
    JOIN Planning AS p WITH ( NOLOCK ) 
        ON v.VOL_Id = p.PLA_Vol_Id
    JOIN Reservation AS r WITH ( NOLOCK ) 
        ON p.PLA_Id = r.RES_Pla_Id
    WHERE 
        r.RES_Client_Id = @IdClient

    OPEN CR_Check_Vols_Par_Id_Client

    FETCH CR_Check_Vols_Par_Id_Client INTO @IdVolExistant

    WHILE @@FETCH_STATUS = 0
    BEGIN

        -- Sélectionne l'id des villes aller (mêmes villes) du dernier vol existant à moins de 60j
        SELECT 
            @IdVilleDepartExistant = v.VOL_Vil_Depart_Id 
            , @IdVilleArriveeExistant = v.VOL_Vil_Arrivee_Id
        FROM Vol v WITH ( NOLOCK ) 
        WHERE 
            v.VOL_Id = @IdVolExistant;

            -- Vérifie s'il existe un vol aller pour le vol inséré
        IF (@IdVilleDepartInseree = @IdVilleArriveeExistant) AND (@IdVilleArriveeInseree = @IdVilleDepartExistant)
        BEGIN

            SELECT 
                @DateVolExistant = p.PLA_Date
            FROM Planning p WITH ( NOLOCK )
            JOIN Reservation AS r WITH ( NOLOCK )
                ON r.RES_Pla_Id = p.PLA_Id
            JOIN Vol AS v WITH ( NOLOCK )
                ON p.PLA_Vol_Id = @IdVolExistant
            JOIN inserted AS i
                ON r.RES_Id = i.RES_Id
            WHERE 
                r.RES_Client_Id = @IdClient;

            -- Vérifie si le vol inséré est à une date de moins de 60 jours du vol retour existant
            IF ( (DATEDIFF(DAY, @DateVolExistant , @DateVolInsere)) < 60  )
            BEGIN

            DECLARE @CheckVolEntre INT
                -- Sélectionne un vol existant entre le vol aller inséré et le vol retour existant
                SELECT 
                    @CheckVolEntre = v.VOL_Id
                FROM Vol v WITH ( NOLOCK )
                JOIN Planning AS p WITH ( NOLOCK )
                    ON v.VOL_Id = p.PLA_Vol_Id
                JOIN Reservation AS r WITH ( NOLOCK )
                    ON p.PLA_Id = r.RES_Pla_Id
                WHERE 
                    r.RES_Client_Id = @IdClient 
                    AND v.VOL_Id BETWEEN @IdVolExistant AND @IdVolInsere;

                -- Vérifie s'il existe un vol existant entre le vol aller inséré et le vol retour existant
                IF (@CheckVolEntre IS NOT NULL)
                BEGIN
                    RAISERROR('Réservation impossible', 1, 601)
                    ROLLBACK TRANSACTION
                    STOP
                END

            END

        END

    END

    CLOSE CR_Check_Vols_Par_Id_Client
    DEALLOCATE CR_Check_Vols_Par_Id_Client

END

首先,即使您的触发器没有超时,它也几乎可以肯定没有按照您的意愿进行。其中大部分归结为公然忽略某些语句中可能存在的多个 return 值。
我也有点担心目前的情况,因为如果我下周出差很短,我似乎无法做周末航班之类的事情!您可能想查看 下一班 航班,但我的知识不足以保证这一点。

也就是说,我 认为(基于对您的数据和意图的一些假设,但您需要检查我的工作)您可以使触发器看起来像这样:

ALTER TRIGGER [dbo].[Tr_Check60j]
ON [dbo].[Reservation]
FOR INSERT -- You could probably do INSTEAD OF and perform the insert here
AS
BEGIN

    SELECT 'Existing Flight'
    FROM Inserted
    JOIN Planning Inserted_planning
      ON Inserted_planning.pla_id = Inserted.res_pla_id
    JOIN Vol Inserted_vol
      ON Inserted_vol.vol_id = Inserted_planning.pla_vol_id
    WHERE EXISTS (
          SELECT 'Existing Flight'    
          FROM Vol Return_vol
          JOIN Planning Return_planning
            ON Return_planning.pla_vol_Id = return_vol.vol_id
               AND Return_planning.pla_date >= DATEADD(day, -60, CAST(Inserted_planning.pla_date AS DATE))
               AND Return_planning.pla_id != Inserted_planning.pla_id
          JOIN Return_reservation
            ON Return_reservation.res_pla_id = Return_planning.pla_Id
               AND Return_reservation.res_client_id = Inserted.res_client_id
               AND Return_reservation.res_id != Inserted.res_id
          JOIN Planning Planning_Other
            ON Planning_Other.pla_date >= Return_planning.pla_date
               AND Planning_Other.pla_date < Inserted_planning.pla_date
               AND Planning_other.pla_id != Inserted_planning.pla_id
               AND Planning_other.pla_id != Return_planning.pla_id
          JOIN Reservation Reservation_Other
            ON Reservation_Other.res_pla_id = Planning_Other.pla_id
               AND Reservation_Other.res_client_id = Inserted.res_client_id
               AND Reservation_other.res_id != Inserted.res_id
               AND Reservation_other.res_id != Return_reservation.res_id
          WHERE Return_vol.vol_vil_depart_id = Inserted_vol.vol_vil_arrivee_id
                AND Return_vol.vil_arrivee_id = Inserted_vol.vol_vil_depart_id)


    IF (@@ROWCOUNT > 0)
    BEGIN
        RAISERROR('Réservation impossible', 1, 601)
        ROLLBACK TRANSACTION
        STOP
    END
END


好的,这是 analysis/change 的推理。让我们按部分分解。

首先,可以合并和简化您的前置查询:

-- For clarity, put DECLAREs on their own lines
DECLARE @IdVolInsere INT;
DECLARE @IdClient INT;
DECLARE @DateVolInsere DATETIME;
DECLARE @IdVilleDepartInseree INT;
DECLARE @IdVilleArriveeInseree INT;

-- ....I'm against prefixing columns with table names, especially _shortened_ table names.
-- Except maybe for id columns.
SELECT @IdVolInsere = Vol.vol_id, 
       @IdClient = Inserted.res_client_Id,
       @IdVilleDepartInseree = Vol.vol_vil_depart_id,
       @IdVilleArriveeInseree = Vol.vol_vil_arrivee_id,
       @DateVolInsere = Planning.pla_Date
FROM Vol -- Single-character aliases make it hard to track tables.
JOIN Planning 
  ON Planning.pla_vol_Id = vol.vol_Id
JOIN Inserted
  ON Inserted.res_pla_id = Planning.pla_id

...既然已经清理完毕,让我们(清理并)看一下游标和其他语句:

DECLARE CR_Check_Vols_Par_Id_Client CURSOR FOR 
SELECT Vol.vol_Id
FROM Vol
JOIN Planning
  ON Planning.pla_vol_Id = Vol.vol_id 
JOIN Reservation
  ON Reservation.res_pla_id = Planning.pla_Id
     AND Reservation.res_client_id = @IdClient

...好的,检查是否有任何 Vol.vol_id 与我们的客户端 ID 相匹配。除了,这是一个直接的 AFTER 触发器,我们将获取刚刚插入的行!我们可能真的不想要那样!

好吧,我们暂时忽略它,并假设我们的最终状态不需要担心它。不过,让我们只删除游标声明,这会将它变成一个集合:

SELECT -- dunno what we need yet
FROM Vol
JOIN Planning
  ON Planning.pla_vol_Id = Vol.vol_id 
JOIN Reservation
  ON Reservation.res_pla_id = Planning.pla_Id
     AND Reservation.res_client_id = @IdClient

...接下来的几行:

    -- Sélectionne l'id des villes aller (mêmes villes) du dernier vol existant à moins de 60j
    SELECT @IdVilleDepartExistant = v.VOL_Vil_Depart_Id FROM Vol v WHERE v.VOL_Id = @IdVolExistant
    SELECT @IdVilleArriveeExistant = v.VOL_Vil_Arrivee_Id FROM Vol v WHERE v.VOL_Id = @IdVolExistant

    -- Vérifie s'il existe un vol aller pour le vol inséré
    IF (@IdVilleDepartInseree = @IdVilleArriveeExistant) AND (@IdVilleArriveeInseree = @IdVilleDepartExistant)

...告诉我我们想根据原始查询中的数据限制我们的集合。这会将集合剪切为仅这些匹配的行。让我们这样做:

SELECT -- dunno what we need yet
FROM Vol
JOIN Planning
  ON Planning.pla_vol_Id = Vol.vol_id 
JOIN Reservation
  ON Reservation.res_pla_id = Planning.pla_Id
     AND Reservation.res_client_id = @IdClient
WHERE Vol.vol_vil_depart_id = @IdVilleArriveeInseree
      AND Vol.vil_arrivee_id = @IdVilleDepartInseree

...下一个查询获取数据的简单条件:

IF ( (DATEDIFF(DAY, @DateVolExistant , @DateVolInsere)) < 60  )

...但是,我们已经有了这些数据,可以将其添加到我们的查询中:

SELECT -- dunno what we need yet
FROM Vol
JOIN Planning
  ON Planning.pla_vol_Id = Vol.vol_id
     AND Planning.pla_date < DATEADD(day, 60, CAST(@DateVolInsere AS DATE))
JOIN Reservation
  ON Reservation.res_pla_id = Planning.pla_Id
     AND Reservation.res_client_id = @IdClient
WHERE Vol.vol_vil_depart_id = @IdVilleArriveeInseree
      AND Vol.vil_arrivee_id = @IdVilleDepartInseree

...不幸的是,下一节的主要条件很可能是废话:

v.VOL_Id BETWEEN @IdVolExistant AND @IdVolInsere

... 那是因为它假设了关于键的非键关系。如果这些是自动生成的代理键,那绝对不是真的。自然密钥最可能的密钥方案不太可能在所有情况下都有用。使用属性数据要好得多,对于这种查询通常是日期。我的猜测是我们可以为此使用 Planning.pla_date

SELECT -- dunno what we need yet
FROM Vol
JOIN Planning
  ON Planning.pla_vol_Id = Vol.vol_id
     AND Planning.pla_date >= DATEADD(day, -60, CAST(@DateVolInsere AS DATE))
JOIN Reservation
  ON Reservation.res_pla_id = Planning.pla_Id
     AND Reservation.res_client_id = @IdClient
JOIN Planning Planning_Other
  ON Planning_Other.pla_date >= Planning.pla_date
     AND Planning_Other.pla_date < @DateVolInsere
     AND Planning_Other.pla_id != Planning.pla_id
JOIN Reservation Reservation_Other
  ON Reservation_Other.res_pla_id = Planning_Other.pla_id
     AND Reservation_Other.res.client_id = @IdClient
     AND Reservation_Other.res_id != Reservation.res_id
WHERE Vol.vol_vil_depart_id = @IdVilleArriveeInseree
      AND Vol.vil_arrivee_id = @IdVilleDepartInseree

... 而在这一点上,我们观察到我们实际上并不关心任何数据,只关心这样一行是否存在。值得庆幸的是,我们可以将其变成一个常规的 EXISTS 子句。