Upsert Deadlock для нетривиального предложения где - PullRequest
1 голос
/ 11 апреля 2019

Я пытаюсь написать некоторый SQL, который выполняет переход по довольно сложным критериям:

BEGIN TRAN;

  UPDATE LocationLog WITH(SERIALIZABLE)
  SET StartTime = CASE
      WHEN StartTime > @StartTime THEN @StartTime
      ELSE StartTime
    END,
    EndTime = CASE
      WHEN EndTime < @EndTime THEN @EndTime
      ELSE EndTime
    END,
    Updated = GETUTCDATE()
  WHERE Who = @Who
    AND (
        StartTime BETWEEN @RangeStart and @RangeEnd
        or
        EndTime BETWEEN @RangeStart and @RangeEnd
    )
    AND cast(Latitude as decimal(8,5)) = cast(@Latitude as decimal(8, 5))
    AND cast(Longitude as decimal(8,5)) = cast(@Longitude as decimal(8, 5))
    AND (Accuracy = @Accuracy or COALESCE(Accuracy, @Accuracy) is NULL)
    AND (Altitude = @Altitude or COALESCE(Altitude, @Altitude) is NULL)
    AND (AltitudeAccuracy = @AltitudeAccuracy or COALESCE(AltitudeAccuracy, @AltitudeAccuracy) is NULL)
    AND (Heading = @Heading or COALESCE(Heading, @Heading) is NULL)
    AND (Speed = @Speed or COALESCE(Speed, @Speed) is NULL);

  IF @@ROWCOUNT = 0
  BEGIN
    INSERT Position(UUID, Who, StartTime, EndTime, Latitude, Longitude, Accuracy, Altitude, AltitudeAccuracy, Heading, Speed, CreatedTime, Updated)
    VALUES (NEWID(), @Who, @StartTime, @EndTime, @Latitude, @Longitude, @Accuracy, @Altitude, @AltitudeAccuracy, @Heading, @Speed, GETUTCDATE(), GETUTCDATE())
  END

COMMIT TRAN

Я использую стандартное «обновление, если @@ rowcount = 0 insert» с транзакциейи сериализуемый, который (насколько я могу судить) такой же, как и Блог Сэма Шафрона «Шаблон вставки или обновления для сервера Sql» , за исключением того, что вместо одного идентификатора столбца я использую большойнабор столбцов-кандидатов, поскольку нет способа программно создать единственный ключ-кандидат.

Я захожу в тупик, когда он вызывается одновременно, и я не могу понять, почему.Просто, чтобы помочь завершить картину, вот определение таблицы:

CREATE TABLE LocationLog (
    [UUID] [uniqueidentifier] NOT NULL CONSTRAINT [PK_Position] PRIMARY KEY NONCLUSTERED ,
    [Who] [uniqueidentifier] NOT NULL INDEX [IX_Who],
    [StartTime] [datetime] NOT NULL,
    [EndTime] [datetime] NULL,
    [Latitude] [decimal](9, 6) NOT NULL,
    [Longitude] [decimal](9, 6) NOT NULL,
    [Accuracy] [float] NULL,
    [Altitude] [float] NULL,
    [AltitudeAccuracy] [float] NULL,
    [Heading] [float] NULL,
    [Speed] [float] NULL,
    [CreatedUtc] [datetime] NOT NULL,
    [UpdatedUtc] [datetime] NOT NULL
)

А вот скрипт, который вызовет много взаимоблокировок с использованием вышеуказанного sql: https://dotnetfiddle.net/xkze6l

Я надеюсь на две вещи в ответе:

  1. Объяснение того, почему это заходит в тупик.(Я прошелся по коду и не понимаю, в чем я ошибся).
  2. Исправление для кода, которое позволит мне сделать это.

Ответы [ 2 ]

0 голосов
/ 12 апреля 2019

Я рекомендую прочитать Условное условие гонки INSERT / UPDATE от Dan Guzman.

Скорее всего, вам нужно добавить HOLDLOCK подсказку к вашему UPDATE заявлению.HOLDLOCK необходим для обеспечения удержания блокировки до конца транзакции.

Насколько я понимаю, ваша подсказка WITH(SERIALIZABLE) относится только к оператору UPDATE и не влияет на следующуюINSERT оператор.

Это объясняет, почему вы получаете взаимоблокировки - блокировка удерживается только на время оператора UPDATE, затем она освобождается, что позволяет втиснуть другой сеанс до INSERT.

Может быть, вам также понадобится UPDLOCK, потому что, хотя это оператор UPDATE, он должен найти строки для обновления в первую очередь, и блокировка должна быть установлена ​​во время этой фазы SELECT, до фазы ОБНОВЛЕНИЯ.

0 голосов
/ 12 апреля 2019

Этот предикат:

AND (
    StartTime BETWEEN @RangeStart and @RangeEnd
    or
    EndTime BETWEEN @RangeStart and @RangeEnd
)
AND cast(Latitude as decimal(8,5)) = cast(@Latitude as decimal(8, 5))
AND cast(Longitude as decimal(8,5)) = cast(@Longitude as decimal(8, 5))

Потребуется сканирование таблицы.Поэтому, если вы собираетесь сканировать таблицу каждый раз, вы можете просто использовать подсказку TABLOCKX вместо SERIALIZABLE.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...