Недавно мне было поручено отладить странную проблему в приложении электронной коммерции. После обновления приложения сайт начал время от времени зависать, и меня отправили на отладку. После проверки журнала событий я обнаружил, что SQL-сервер за пару минут написал ~ 200 000 событий с сообщением о том, что ограничение не выполнено. После долгих отладок и некоторой трассировки я нашел виновника. Я удалил некоторый ненужный код и немного его почистил, но по сути это
WHILE EXISTS (SELECT * FROM ShoppingCartItem WHERE ShoppingCartItem.PurchID = @PurchID)
BEGIN
SELECT TOP 1
@TmpGFSID = ShoppingCartItem.GFSID,
@TmpQuantity = ShoppingCartItem.Quantity,
@TmpShoppingCartItemID = ShoppingCartItem.ShoppingCartItemID,
FROM
ShoppingCartItem INNER JOIN GoodsForSale on ShoppingCartItem.GFSID = GoodsForSale.GFSID
WHERE ShoppingCartItem.PurchID = @PurchID
EXEC @ErrorCode = spGoodsForSale_ReverseReservations @TmpGFSID, @TmpQuantity
IF @ErrorCode <> 0
BEGIN
Goto Cleanup
END
DELETE FROM ShoppingCartItem WHERE ShoppingCartItem.ShoppingCartItemID = @TmpShoppingCartItemID
-- @@ROWCOUNT is 1 after this
END
Факты:
- Есть только одна или две записи, соответствующие первому предложению выбора
- RowCount из оператора DELETE указывает, что он был удален
- Предложение WHILE будет зациклено навсегда
Процедура была переписана для выбора строк, которые должны быть удалены во временную таблицу в памяти, чтобы немедленная проблема была решена, но это действительно вызвало мое любопытство.
Почему он зацикливается навсегда?
Уточнение : удаление не завершается неудачно (@@ rowcount равно 1 после удаления stmt при отладке)
Разъяснение 2 : не должно иметь значения, упорядочен ли оператор SELECT TOP ... каким-либо конкретным полем, поскольку запись с возвращенным идентификатором будет удалена, поэтому в следующем цикле она должна получить другую запись .
Обновление : После проверки журналов подрывной деятельности я обнаружил, что коммит-виновник, который сделал эту хранимую процедуру, вышел из строя. Единственное реальное различие, которое я могу найти, состоит в том, что ранее в операторе SELECT TOP 1 не было никакого соединения, то есть без того, чтобы это соединение работало без каких-либо операторов транзакций, окружающих удаление. Похоже, это было введение объединения, которое сделало SQL-сервер более разборчивым.
Обновление пояснений : brien указало, что нет необходимости в объединении, но мы действительно используем некоторые поля из таблицы GoodsForSale, но я удалил их, чтобы просто сохранить код так что мы можем сосредоточиться на проблеме под рукой