Интригующая проблема настройки производительности SQL Server - PullRequest
3 голосов
/ 23 октября 2009

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

У нас есть ночной процесс, который запускается и запускается хранимой процедурой, которая вызывает много-много других хранимых процедур. Множество вызываемых хранимых процедур вызывают другие и т. Д. Я рассмотрел некоторые из вызываемых процедур, и в них есть всякие пугающие сложные вещи, такие как обработка строк XML, ненужное чрезмерное использование курсоров, чрезмерное использование подсказок NOLOCK, редкое использование обработки на основе множеств и т. д. - список можно продолжать, это ужасно.

Этот ночной процесс в нашей производственной среде занимает в среднем 1:15. Иногда для запуска требуется 2 часа, что недопустимо. Я создал тестовую среду на оборудовании, идентичном производственному, и запустил процесс. Это заняло 45 минут, когда я впервые запустил его. Если я восстанавливаю базу данных в ту же точку и запускаю ее снова, это займет больше времени: на самом деле, если я повторю это действие несколько раз (восстановление и повторный запуск), процесс будет прогрессивно дольше, пока он не достигнет примерно 2 часов. Это действительно озадачивает меня, потому что я каждый раз восстанавливаю базу данных до одной и той же точки. На сервере нет других пользовательских баз данных.

Я подумал о двух направлениях расследования:

  1. Планы запросов и подмена параметров
  2. Tempdb

В качестве теста я перезапустил SQL Server, чтобы очистить кеш и базу данных tempdb, и повторно запустил процесс с тем же восстановлением базы данных. Процесс занял 45 минут. Я повторил это несколько раз, чтобы убедиться, что это повторяется - снова это заняло 45 минут каждый раз. Затем я предпринял несколько тестов, чтобы попытаться изолировать удивительное увеличение времени выполнения, когда SQL Server не перезапускается:

  1. Запустить начальную хранимую процедуру с RECOMPILE
  2. Перед запуском процедуры выполните executre DBCC FREEPROCCACHE, чтобы очистить кеш процедуры
  3. Перед запуском процедуры выполните CHECKPOINT, а затем DBCC DROPCLEANBUFFERS, чтобы убедиться, что кэш был пустым и чистым
  4. Выполнен следующий скрипт, чтобы гарантировать, что все хранимые процедуры были помечены для перекомпиляции:

    DECLARE @proc_schema SYSNAME
    DECLARE @proc_name SYSNAME
    
    DECLARE prcCsr CURSOR local
        FOR SELECT  specific_schema,
                    specific_name
            FROM    INFORMATION_SCHEMA.routines
            WHERE   routine_type = 'PROCEDURE'
    
    OPEN prcCsr
    
    FETCH NEXT FROM prcCsr INTO @proc_schema, @proc_name
    
    DECLARE @stmt NVARCHAR(MAX)
    WHILE @@FETCH_STATUS = 0
        BEGIN
            SET @stmt = N'exec sp_recompile ''[' + @proc_schema + '].['
                + @proc_name + ']'''
    --        PRINT @stmt   -- DEBUG
            EXEC ( @stmt
                )
    
            FETCH NEXT FROM prcCsr INTO @proc_schema, @proc_name
        END
    

Во всех вышеперечисленных тестах процедура выполняется дольше и дольше с тем же восстановлением базы данных. Я действительно в растерянности сейчас относительно того, что попробовать. Изучение кода на данный момент является одним из вариантов, но на самом деле на его оптимизацию уйдет 3-6 месяцев, поскольку есть много возможностей для улучшения. В чем я действительно заинтересован, так это в том, почему время выполнения процедуры увеличивается каждый раз, когда выполняется восстановление базы данных, даже когда очищены процедуры и буферные кэши?

Я также исследовал базу данных tempdb и попытался очистить там старые таблицы, как описано в моем другом посте stackoverflow, но я не могу вручную очистить временные таблицы, которые были созданы из переменных таблицы, и похоже, что они хочу исчезнуть самостоятельно (даже после того, как покинул их на 24 часа).

Будем весьма благодарны за любые идеи или предложения по дальнейшему тестированию. Я использую 64-разрядную версию Enterprise Edition SQL Server 2005 с пакетом обновления 3 (SP3) на Windows 2003 R2 Ent. редакция кластера.

С уважением, Mark.

Ответы [ 12 ]

2 голосов
/ 30 октября 2009

Можете ли вы попробовать следующий сценарий на тестовом сервере:

  1. Сделайте две копии базы данных на сервере: [A] и [B]. [A] - это база данных, [B] - копия.
  2. Перезагрузить сервер
  3. Запустите ваш процесс
  4. Удалить базу данных [A]
  5. Переименуйте [B] в [A]
  6. Запустите ваш процесс

Это было бы как горячий обмен базами данных. Если второй запуск занимает больше времени, происходит что-то на уровне сервера (tempdb, память, ввод-вывод и т. Д.). Если второй запуск занимает примерно то же время, то проблема заключается в уровне базы данных (блокировки, фрагментация индекса и т. Д.).

Удачи!

2 голосов
/ 29 октября 2009

Mark-

Таким образом, может потребоваться 3-6 месяцев, чтобы полностью переписать эту процедуру, но это не значит, что вы не сможете провести относительно быструю оптимизацию производительности.

Некоторые из подпрограмм, которые я должен поддерживать, запускаются в течение 30 часов +, я был бы в восторге, чтобы заставить их работать в течение 2 часов !! вид оптимизации, которую вы выполняете в этих подпрограммах, немного отличается от вашей обычной базы данных OLTP:

  1. Получите трассировку всего процесса, убедившись, что захватили события SP: StmtCompleted и SQL: StmtCompleted. Обязательно установите фильтр на Длительность (> 10 мс или что-то в этом роде), чтобы исключить все быстрые несущественные утверждения.

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

    (A) Горстка отдельных запросов / заявлений ответственна за большую часть времени процедуры (хорошие новости)

    (B) Множество подобных утверждений занимают короткое время, но вместе они составляют длительное время.

В сценарии (A) просто сфокусируйте ваше внимание на этих запросах. Оптимизируйте их, используя индексы или другие стандартные методы. Я настоятельно рекомендую книгу Дэна Тау «Настройка SQL» для мощной техники оптимизации запросов, особенно беспорядочных со сложными объединениями.

В сценарии (B) отойдите немного назад и посмотрите на набор операторов в целом. Они все похожи в некотором роде? Можете ли вы добавить индекс для ключевой общей таблицы, которая улучшит их все? Можете ли вы исключить цикл, который выполняет 10 000 динамических запросов, и вместо этого выполнять один запрос на основе набора?

Еще две другие возможности, я полагаю:

(C) 15 000 совершенно разных динамических операторов SQL, каждый из которых требует своей кропотливой оптимизации. В этом случае попытайтесь сосредоточиться на оптимизации на уровне сервера, такой как улучшения на основе ввода / вывода, которые принесут пользу всем им.

(D) Что-то еще странное происходит с TempDB или что-то неправильно настроено на сервере. Больше я ничего не могу здесь сказать, кроме как найти проблему и исправить ее!

Надеюсь, это поможет.

2 голосов
/ 29 октября 2009

Просматривая все сообщения на сегодняшний день и связанный с ними вопрос, определенно кажется, что ваше самое сильное преимущество - загадка этих объектов tempdb. Некоторые наводящие вопросы:

  • После нового запуска, после запуска процесса, сколько объектов находится в базе данных tempdb? Это один и тот же номер после каждого нового старта?
  • Увеличиваются ли цифры после «последовательных» прогонов? Они растут с одинаковой скоростью?
  • Можете ли вы определить, занимают ли они место?
  • В таком случае, ваши файлы tempdb растут с каждым последующим запуском вашего процесса?

Я перешел по ссылкам, но не нашел каких-либо справочных обсуждений по актуальной проблеме. Возможно, вы захотите поднять эту проблему на форумах Microsoft SQL Technet здесь - они могут быть очень хороши с абстрактными вещами. (Если ничего не помогло, вы можете открыть дело в технической поддержке MS. Это может занять несколько дней, но очень вероятно, что они все выяснят. А если это ошибка MS, они вернут вам деньги!)

Вы сказали, что переписать код нельзя. Однако, если злоупотребление временными таблицами является фактором, идентификация и рефакторинг этих частей кода в первую очередь может помочь. Чтобы узнать, какие это могут быть, запустите SQL Profiler во время выполнения вашего процесса. Такая работа, увы, субъективна и очень итеративна (то есть вы вряд ли когда-либо получите только правильный набор счетчиков на первом проходе). Некоторые мысли:

  • Начать с отслеживания SP: запущен, чтобы отслеживать, какие хранимые процедуры вызываются.
  • SQL Profiler может использоваться для группировки данных; это неудобно, и я не уверен, как описать это простым текстом, но при правильной настройке вы получите дисплей Profiler, показывающий, сколько раз каждая процедура была. В идеале это показывало бы наиболее часто вызываемые процессы, и вы можете анализировать их на предмет злоупотребления временными таблицами и рефакторинга по мере необходимости.
  • Если ничего не происходит, вы можете отследить SP: StmtStarting и сделать то же самое для отдельных операторов. Проблема здесь заключается в том, что при выполнении спагетти-кода в течение 2 +/- часов вы можете нехватать места на диске, и анализ сотен МБ данных трассировки может стать кошмаром. (Подсказка: загрузите его в таблицу, создайте индексы, а затем осторожно удалите излишки.) Опять же, цель будет заключаться в том, чтобы идентифицировать чрезмерно используемый / злоупотребленный код временной таблицы для рефакторинга.
2 голосов
/ 23 октября 2009

Это может быть вызвано тем, что в процессе происходит утечка XML-документов. Это может привести к тому, что SQL Server будет использовать больше памяти, а часть этого может быть записана в файл подкачки на диске, что приведет к замедлению процесса.

Код, который создает документ XML, выглядит следующим образом:

EXEC sp_xml_preparedocument @idoc OUTPUT, @strXML

Вытекает, если нет соответствующих:

EXEC sp_xml_removedocument @idoc

XML-документы - это COM-объекты, хранящиеся за пределами сконфигурированной памяти SQL Server. Даже если вы установите в SQL Server максимум 5 ГБ, утечка XML-документов увеличивает использование памяти сверх этого.

1 голос
/ 29 октября 2009

Запустите следующий скрипт в начале теста, а затем после каждой итерации:

select sum(single_pages_kb) as sum_bp_kb
  , sum(multi_pages_kb) as sum_va_kb
  , type
from sys.dm_os_memory_clerks
group by type
having sum(single_pages_kb+multi_pages_kb) > 16
order by sum(single_pages_kb+multi_pages_kb) desc

select sum(total_pages), type_desc
from tempdb.sys.allocation_units
group by type_desc;

select * from sys.dm_os_performance_counters
where counter_name in (
  'Log Truncations'
  ,'Log Growths'
  ,'Log Shrinks'
  ,'Data File(s) Size (KB)'
  ,'Log File(s) Size (KB)'
  ,'Active Temp Tables');

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

0 голосов
/ 02 ноября 2009

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

Я запустил трассировку на стороне сервера, чтобы попытаться изолировать хранимые процессы, которые работали медленнее между итерациями. То, что я нашел, удивило меня. 96 хранимых процедур участвуют в процессе. Во второй раз большинство этих хранимых процедур работало медленнее - около 50 из них. Остальные были очень быстрыми и не влияли на общее время, а на самом деле некоторые из них работали немного быстрее (как и следовало ожидать).

Я перевел экземпляр базы данных на другой узел в моем кластере и провел там тесты с точно такими же результатами - так что я могу исключить любые различия ОС между узлами кластера - при создании кластеров я очень сознательно создавал их идентично .

1100 временных таблиц создаются во время процесса и сохраняются после его завершения - все это переменные таблицы, и я нашел способ их удалить. Запуск sp_recompile для каждого процесса и функции в базе данных привел к очистке всех временных таблиц. Однако это не вообще улучшило время выполнения. Единственное, что помогает времени выполнения, - это перезапуск службы SQL Server. К сожалению, сейчас у меня нет времени для дальнейшего расследования - у меня есть другая работа, но я хотел бы продолжать с этим. Возможно, я вернусь к этому позже, если у меня будет свободное время. Тем временем, однако, я должен признать свое поражение без решения и без щедрости.

Еще раз спасибо всем.

0 голосов
/ 31 октября 2009

Это длинные кадры:

  • Быстро просматривайте все хранимые процедуры для вещей, которые необычный и SQL Server не должен действительно делать, например, отправка электронная почта или запись файлов и т. д. SQL, пытающийся отправить электронную почту на несуществующий почтовый сервер, может вызвать задержки.
  • Другая вещь, которую нужно иметь в виду, это что при восстановлении базы данных перед каждым тестом возможно твой диск становится все более фрагментированным (не действительно уверен в этом, хотя). Так это может объяснить, почему время выполнения увеличивается каждый раз, пока они не выходят на плато.
0 голосов
/ 29 октября 2009

У меня нет ответа, но есть некоторые идеи о том, что бы я сделал, чтобы изолировать подобные проблемы.

Во-первых, я бы делал снимки sys.dm_os_wait_stats до и после каждого выполнения. Вы вычитаете 2 снимка (получаете дельты) и смотрите, заметен ли какой-либо конкретный WAIT или ухудшается с каждым прогоном. Простой способ вычисления дельт состоит в том, чтобы скопировать значения sys.dm_os_wait_stats в таблицы Excel и использовать VLOOKUP () для вычитания соответствующих значений. Я использовал эту технику расследования сотни раз. Вы не знаете, какой аспект SQL Server одержим ?! Пусть SQL Server «скажет» вам через sys.dm_os_wait_stats!

Другая вещь, которую я мог бы попробовать, это настроить поведение цикла, чтобы понять, если последующие более медленные исполнения демонстрируют постоянную пропускную способность для всех записей от начала до конца или это замедляется только для определенных sproc в INFORMATION_SCHEMA.routines ... 2 метода для изучения этого:

1) Добавьте предложение «top N» в SQL SELECT, такое как «top 100» или «top 1000» (создайте искусственный лимит), чтобы увидеть, получаете ли вы последующие замедления для всех сценариев подсчета записей ... или .. ... Вы получаете замедления только тогда, когда набор результатов курсора достаточно велик, чтобы включать вызывающий спрок.

2) Вместо добавления «top N» вы можете добавить дополнительные операторы печати (инструментарий) для вычисления пропускной способности при обработке.

Конечно, вы можете сделать комбинацию обоих.

Может быть, эта диагностика приблизит вас к основной причине.

Отредактировано, чтобы добавить: Между прочим, SQL2008 имеет новый монитор производительности, который позволяет легко "смотреть" цифры sys.dm_os_wait_stats. Однако для SQL2005 вам придется вручную вычислять дельты с помощью Excel или скрипта.

0 голосов
/ 23 октября 2009

Курсоры не являются усилителями производительности, другие обращаются к этому. (не ваше решение)

Посмотрите на временные таблицы использования / управления. Это глобальные временные таблицы или сеансовые / локальные временные таблицы? То, что они торчат, выглядит интересно. Tempdb блокируется при создании временных таблиц, которые могут быть частью проблемы.

Локальные временные таблицы (синтаксис #mytable) должны исчезнуть, когда сеанс выходит из области видимости, но вы ДОЛЖНЫ удалить их (выпустить рано), чтобы освободить ресурсы.

Использование локальных временных таблиц в транзакции, а затем отмена без COMMIT / ROLLBACK может увеличить блокировку в базе данных tempdb, что приведет к проблемам с производительностью. Говоря о транзакциях - это приведет к блокировкам в syscolumns, sysindexes и т. Д., Если временные таблицы будут созданы в транзакциях - таким образом, другие очереди заблокированы от использования того же запроса.

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

Если вам нужны временные таблицы (для устранения курсоров :), тогда избегайте SELECT INTO - чтобы избежать блокировок системных объектов.

Следует избегать использования глобальных временных таблиц (## myglobaltable синтаксис), так как может возникнуть и возникнуть проблема с доступом к нескольким сеансам (таблица зависает до тех пор, пока все сеансы не очищаются), и, по крайней мере для меня, не предлагает никаких аддитивных логических значений (смотрите вместо использования постоянного стола). Вопрос глобальный, есть ли процедуры блокировки?

Много ли разреженных временных таблиц (растут с большими данными, но содержат в себе меньшие наборы данных?)

Microsoft SQL Server Book Online, «Подумайте об использовании табличных переменных вместо временных таблиц. Временные таблицы полезны в тех случаях, когда для них необходимо явно создавать индексы или когда значения таблиц должны быть видны в нескольких хранимых процедурах или функциях. В общем случае переменные таблицы способствуют более эффективной обработке запросов ».

Конечно, если временная таблица нуждается в индексах, табличные переменные не являются опцией.

0 голосов
/ 23 октября 2009

Я бы заносил информацию в log_table и время, необходимое для выполнения каждого шага ... что поможет вам сузить проблему, а также поможет вам постепенно улучшить процесс, решая его по одному (от улучшения процедур, самый длинный).

Лучший способ - просто вставить начало и конец каждого процесса.

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