2015-06-08 1 views
3

Я унаследовал сохранение хранимой процедуры, которая выполняется каждую ночь с помощью задания агента SQL. Он работает хорошо в течение нескольких месяцев, но все внезапные последние ночи повторяют какую-то работу и пропустили какую-то работу.SQL Server - Удалить из переменных таблицы не мгновенно?

Работа выполняется в середине ночи, и на данный момент пользователей нет. Я восстановил резервную копию базы данных непосредственно перед проблемным запуском на тестовый сервер, перезапустил процедуру, и все сработало нормально. Это небольшие данные, может быть, 100-200 строк за ночь.

Вот представление одной из петель в порядке, где проблема была обнаружена:

DECLARE @uniqueId int 
DECLARE @examId int 

DECLARE @TempSingleContactTable TABLE 
(
    uniqueId int IDENTITY(1,1) PRIMARY KEY, 
    examId int not null, 
    contactEmail nvarchar(max) null, 
) 

[data inserted into @TempSingleContactTable] 

WHILE EXISTS (SELECT * FROM @TempSingleContactTable) 
    BEGIN 
    Select top 1 @uniqueId = uniqueId, 
     @examId = examID, from @TempSingleContactTable 

    [*****PROBLEM HERE- this line with same value for @examId ran multiple times, but eventually continued] 


    DELETE FROM @TempSingleContactTable WHERE examID = @examId 
    END 

Единственное, что я могу видеть, что может привести к проблеме выше, если DELETE вызов не работает , Возможно ли, что вызов DELETE против переменной таблицы не мгновенен?


EDIT: Любая информация о том, что может быть причиной Удалить из @TempSingleContactTable на провал спорадически очень ценится.


EDIT 2: Дополнительное исследование показало, что это автоматизировано один раз по ночам процедура не удалась так же, как два раза в течение двух месяцев. Интересно, что каждый раз, когда он проваливался, прогон прошлой ночи не изменял никаких данных, и это всегда должно было быть. К сожалению, нет информации о регистрации, чтобы определить, что могло вызвать проблемы прошлых ночей. Похоже, что они должны быть связаны, хотя это может быть красная селедка. Я добавил регистрацию с надеждой получить фактическую основную причину.

+1

Почему вы используете '@ examId' в качестве критерия в вашем заявлении о стирании, если он не гарантирован уникальным? '@ uniqueId' - лучший кандидат, так как это ваш первичный ключ. – Kritner

+0

@Kritner, потому что работа, выполняемая в этом цикле, должна выполняться только один раз для идентификатора экзамена, и может быть несколько записей для одного контакта с тем же идентификатором экзамена. –

ответ

3

От того, что похоже на унаследованный «указатель бедных». Как-то люди слышали, что курсоры «злые», и затем они приходят к этому = ( . Я не собираюсь начинать дискуссию о том, как на основе набора предпочтительнее операций на основе курсора (чтение: по очереди). ситуаций, у вас просто нет выбора, возможно, это тоже не так.

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

На первый взгляд, эквивалентный курсор будет таким:

DECLARE @uniqueId int 
DECLARE @examId int 

DECLARE @TempSingleContactTable TABLE 
(
    uniqueId int IDENTITY(1,1) PRIMARY KEY, 
    examId int not null, 
    contactEmail nvarchar(max) null 
) 

-- [data inserted into @TempSingleContactTable] 

DECLARE exams_loop CURSOR LOCAL FAST_FORWARD 
    FOR SELECT uniqueId, examID 
      FROM @TempSingleContactTable 
OPEN exams_loop 
FETCH NEXT FROM exams_loop INTO @uniqueId, @examId 
WHILE @@FETCH_STATUS = 0 
    BEGIN 

     -- internals... 

     FETCH NEXT FROM exams_loop INTO @uniqueId, @examId 
    END 
CLOSE exams_loop 
DEALLOCATE exams_loop 

Но если смотреть больше гр Ужасно есть улов: конец вашей петли удаляет все записи для заданного examID. Поэтому, если есть несколько записей с тем же examID, это означает, что некоторые значения uniqueID будут пропущены. (Примечание: это даже не уверены, какие из них, никогда не поддавайтесь искушению полагаться на них, находясь в естественном порядке, потому что есть PK на поле!)

Таким образом, следующий код является лучшей заменой:

DECLARE exams_loop CURSOR LOCAL FAST_FORWARD 
    FOR SELECT MIN(uniqueId), examID 
      FROM @TempSingleContactTable 
     GROUP BY examID 
OPEN exams_loop 
FETCH NEXT FROM exams_loop INTO @uniqueId, @examId 
WHILE @@FETCH_STATUS = 0 
    BEGIN 

     -- internals... 

     FETCH NEXT FROM exams_loop INTO @uniqueId, @examId 
    END 
CLOSE exams_loop 
DEALLOCATE exams_loop 

На этот раз это будет самый низкий uniqueID, который выигрывает вместо случайного, но, со всей справедливостью, я думаю, что повторяемость (о чем мы говорим здесь действительно) должна быть предпочтительнее случайности.

Во всяком случае, в итоге:

  • достаточно использовать реальный курсор вместо замены бедного-человека, потому что это плохая замена начать с
  • , если вы действительно хотите сохранить цикл, как это правильно Теперь, измените таблицу-определение к этому:

=>

DECLARE @TempSingleContactTable TABLE 
(
    uniqueId int IDENTITY(1,1) PRIMARY KEY, 
    examId int not null UNIQUE (examId, uniqueId), 
    contactEmail nvarchar(max) null 
) 

таким образом вы будете хотя бы иметь индекс в поле, когда вы удаляете. (несмотря на то, что я очень не поощряю интенсивные операции над @ table-variables, они, как правило, уходят на юг, когда вы помещаете туда «средние» объемы данных, не говоря уже о том, чтобы начать делать на нем операции ... # temp-tables гораздо больше надежный в этом отношении!)

+0

Интересное чтение кстати: http://sqlblog.com/blogs/hugo_kornelis/archive/2007/11/28/poor-men-see-sharp-ndash-more-cursor-optimization.aspx – deroby

+0

Не могли бы вы прокомментировать, что Основная причина заключается в том, почему, по-видимому, иногда не работает существующее существующее удаление (DELETE FROM @ TempSingleContactTable WHERE examID = @ examId)? –

+0

Если честно, я не могу. Вероятно, это комбинация вещей, но основным виновником является переменная @table. Эти вещи прекрасны, чтобы временно хранить некоторые данные «сбоку», но для фактической «работы» они не лучший инструмент. (Нет индексов, статистики, обработки транзакций и т. Д.). Мое лучшее предположение заключается в том, что сгенерированный план запроса для оператора DELETE выходит на юг, потому что оптимизатор имеет (почти) никакой информации для принятия своих решений. Почему это случается только изредка, может быть сочетание неудач и причудливых данных, но, честно говоря, я не знаю. – deroby

Смежные вопросы