Хранимая процедура с циклом курсора FAST_FORWARD начинается быстро, заканчивается медленно - PullRequest
2 голосов
/ 12 октября 2010

У меня есть хранимая процедура, которая использует курсор FAST_FORWARD для хронологического зацикливания набора ~ 300 тыс. Записей и присваивает их наборам объявлений на основе состояния множества работающих переменных и флагов, частично реализованных в виде табличных переменных. Я много думал о том, как сделать это на основе множества, и я просто не могу это сделать. Так что я застрял с подходом курсора, и мне нужно оптимизировать этот код.

Что я заметил, так это то, что первые 10% прогресса загружаются и обрабатываются очень быстро (2000 строк / сек), при скорости около 20% он замедляется примерно до 300 строк / сек, и в конце концов он замедляется до около 60 строк / сек.

ИМО это может быть связано с 4 причинами:

  • Курсор замедляется, что я считаю маловероятным с курсором FAST_FORWARD
  • Обработка замедляется. Для моих «групп» я использую переменные таблицы, в которые я вставляю, обновляю и удаляю. В любой данный момент есть макс. около 10 строк в этих переменных.
  • Вставка в целевые таблицы замедляется. Я не понимаю, почему это так, у меня не определены триггеры, и они являются просто обычными таблицами.
  • Злая магия

Либо тот, либо мой счетчик процентов не работает:

SET @curprogress = @curprogress + 1
IF (@curprogress - ((@totprogress / 100) * (FLOOR(@curprogress * 100 / @totprogress)))) BETWEEN 0 AND 1 BEGIN
    SET @msg = CAST(FLOOR(@curprogress * 100 / @totprogress) AS VARCHAR)
    RAISERROR('%s%s', 0, 1, @msg, '%...') WITH NOWAIT;
END

Кто-нибудь знает, что искать и как ускорить этот запрос?

Символическая часть моего кода:

WHILE....
-- Fetch new record to be assigned to one of the open declaration sets
FETCH NEXT INTO @row_field1, @row_field2....
IF (@flag2 = 1) AND ((@flag1 = 0) OR (@row_field1 <> @prevrow_field1)) 
BEGIN
    -- Logging info: we are closing a child declaration set
    INSERT INTO @logtable SELECT '--> LOG MESSAGE'
    INSERT INTO @logtable
    SELECT format_message(@row_field1, @calc_field2, field3...)
    FROM @runningtable_sub S LEFT JOIN @runningtable_main M ON S.MainID = M.ID

    -- Update enddate of parent
    UPDATE M SET M.enddate = DATEADD(day,365,S.enddate)
    FROM @runningtable_sub S
    LEFT JOIN @runningtable_main M
    ON S.MainID = M.ID

    -- close and save child
    INSERT INTO outputtable_main
    SELECT @field1, COALESCE(Z.Field1,'NULL'), S.startdate, S.enddate,
        M.Startdate, M.Enddate
    FROM @runningtable_sub S 
    LEFT JOIN @runningtable_main M ON S.MainID = M.ID

    -- delete child from running table
    DELETE FROM @runningtable_sub WHERE S.enddate < @curdate
END

1 Ответ

0 голосов
/ 13 октября 2010

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

Случайные наблюдения:

  1. ВашФункция format_message (), безусловно, собака;все UDFs есть.Но сколько строк вы вставляете за проход?
  2. @ runningtable_main никогда не очищается.
  3. обновления дорогие
  4. удаления дорогие.Если вы используете временные таблицы и переделываете свою реализацию, вы можете усечь вместо удаления

Чтобы самим разобраться в этом, добавьте инструментарий:

DECLARE @now DATETIME, @duration INT, @rowcount INT

WHILE....
-- Fetch new record to be assigned to one of the open declaration sets
FETCH NEXT INTO @row_field1, @row_field2....
IF (@flag2 = 1) AND ((@flag1 = 0) OR (@row_field1 <> @prevrow_field1)) 
BEGIN
    PRINT '---------------'

    -- Logging info: we are closing a child declaration set
    INSERT INTO @logtable SELECT '--> LOG MESSAGE'
    SET @now = GETDATE()
    INSERT INTO @logtable
    SELECT format_message(@row_field1, @calc_field2, field3...)
    FROM @runningtable_sub S LEFT JOIN @runningtable_main M ON S.MainID = M.ID
    SELECT @rowcount = @@ROWCOUNT, @duration = DATEDIFF(ms,@now,GETDATE)
    RAISERROR('%i row(s) inserted into @logtable, %i milliseconds',-1,-1,@rowcount,@duration) WITH NOWAIT

    -- Update enddate of parent
    SET @now = GETDATE()
    UPDATE M SET M.enddate = DATEADD(day,365,S.enddate)
    FROM @runningtable_sub S
    LEFT JOIN @runningtable_main M
    ON S.MainID = M.ID
    SELECT @rowcount = @@ROWCOUNT, @duration = DATEDIFF(ms,@now,GETDATE)
    RAISERROR('%i row(s) updated in @runningtable_main, %i milliseconds',-1,-1,@rowcount,@duration) WITH NOWAIT

    -- close and save child
    SET @now = GETDATE()
    INSERT INTO outputtable_main
    SELECT @field1, COALESCE(Z.Field1,'NULL'), S.startdate, S.enddate,
        M.Startdate, M.Enddate
    FROM @runningtable_sub S 
    LEFT JOIN @runningtable_main M ON S.MainID = M.ID
    SELECT @rowcount = @@ROWCOUNT, @duration = DATEDIFF(ms,@now,GETDATE)
    RAISERROR('%i row(s) inserted into outputtable_main, %i milliseconds',-1,-1,@rowcount,@duration) WITH NOWAIT

    -- delete child from running table
    SET @now = GETDATE()
    DELETE FROM @runningtable_sub WHERE S.enddate < @curdate
    SELECT @rowcount = @@ROWCOUNT, @duration = DATEDIFF(ms,@now,GETDATE)
    RAISERROR('%i row(s) deleted from @runningtable_sub, %i milliseconds',-1,-1,@rowcount,@duration) WITH NOWAIT
END

Если вы включилиВесь код (включая объявление курсора), а не просто символический отрывок, кто-то здесь, возможно, мог бы переработать, чтобы вы были намного эффективнее и / или избегали использования курсоров вместе.

...