SQL: Что замедляет вставки, если не процессор или ввод-вывод?

19

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

Например, я запустил скрипт, найденный на этой странице , за исключением того, что я добавил BEGIN TRAN и COMMIT вокруг цикла. В лучшем случае я видел, как использование диска достигало 7 Мбит / с, а процессор едва касался 5%. На сервере установлено 64 ГБ, и он использует 10. Общее время выполнения составило 2 минуты 15 секунд для первого вызова и примерно до 1 минуты для последующих вызовов. База данных находится на простом восстановлении и не работала во время теста. Я бросил стол между каждым звонком.

Почему такой простой скрипт такой медленный? Аппаратное обеспечение практически не используется. Как специализированные инструменты для тестирования дисков, так и SQLIO указывают на то, что твердотельный накопитель работает правильно со скоростью более 500 Мбит / с для чтения и записи. Я понимаю, что случайные записи выполняются медленнее, чем последовательные записи, но я ожидаю, что простая вставка, подобная этой, в таблицу без кластерной индексации будет намного быстрее.

В конечном итоге наш сценарий намного сложнее, но я чувствую, что сначала мне нужно разобраться в простом случае. В двух словах, наше приложение удаляет старые данные, затем использует SqlBulkCopy для копирования новых данных в промежуточные таблицы, выполняет некоторую фильтрацию и, наконец, использует MERGE и / или INSERT INTO в зависимости от случаев для копирования данных в финальные таблицы.

-> РЕДАКТИРОВАТЬ 1: Я следовал процедуре, связанной Мартином Смитом, и получил следующий результат:

[Wait Type]  [Wait Count] [Total Wait (ms)] [T. Resource Wait (ms)] [T. Signal Wait (ms)]
NETWORK_IO          5008              46735                 46587        148
LOGBUFFER           901               5994                  5977         17
PAGELATCH_UP        40                866                   865          1
SOS_SCHEDULER_YIELD 53279             219                   121          98
WRITELOG            5                 145                   145          0
PAGEIOLATCH_UP      4                 58                    58           0
LATCH_SH            5                 0                     0            0

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

-> РЕДАКТИРОВАТЬ 2: Я создал 20 ГБ RAM-диск и оттуда смонтировал базу данных. Лучшее время, которое у меня было на SSD - 48 с, а с RAM-диска оно уменьшилось до 37 секунд. NETWORK_IO по-прежнему самое большое ожидание. Максимальная скорость записи на RAM-диск составляла около 250 Мбит / с, тогда как он способен обрабатывать несколько гигабайт в секунду. Он по-прежнему не использует много процессора, так что же удерживает SQL?

Djof
источник
3
Это NETWORK_IOмогут быть сообщения из 3 миллионов сообщений, "затронутых 1 строкой". Вы пробовали добавить SET NOCOUNT ONв скрипт?
Мартин Смит
Да, я добавил NOCOUNT.
Djof
2
Странный. Я бы тогда не ожидал многого от сетевой активности. Вы удаляли старые расширенные файлы событий между запусками? Скрипт, который их читает, использует подстановочный знак, EE_WaitStats*.xelтак что старые будут загрязнять ваши результаты.
Мартин Смит
Хороший звонок, я буду обновлять результаты завтра.
Djof

Ответы:

9

Я знаю, что это старый Вопрос, но он все еще может помочь поисковикам, и эта проблема возникает время от времени.

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

В моем случае для вставки 3 миллионов строк требуется 36 секунд. Это означает, что 36/30000000 = 0,000012 секунд на строку. Это довольно быстро. В моей системе просто требуется 0,000012, чтобы пройти все необходимые шаги.

Единственный способ сделать это быстрее - запустить второй сеанс параллельно.

Если я начну 2 сеанса параллельно, оба будут делать 15 миллионов вставок. Они оба финишируют за 18 секунд. Я мог бы масштабировать больше, но мои текущие настройки теста достигают 95% ЦП с двумя параллельными сессиями, поэтому выполнение 3 исказит результаты, поскольку я попаду в узкое место ЦП.

Если я начну 2 параллельных сеанса, вставляя 3 миллиона строк, они оба завершат работу за 39 секунд. так что теперь 6 миллионов строк за 39 секунд.

Хорошо, это все еще оставляет нас с ожиданием NETWORK_IO.

Ожидания NETWORK_IO добавляются тем фактом, что вы используете расширенные события для их отслеживания. В моем случае вставка занимает 36 секунд (в среднем). При использовании расширенного способа события (по ссылке выше в самом первом комментарии) это то, что регистрируется:

Wait Type             Wait Count  Total Wait Time (ms) Total Resource Wait Time (ms) Total Signal Wait Time (ms)
NETWORK_IO            3455        68808                68802                         6
PAGEIOLATCH_SH        3           64                   64                            0
PAGEIOLATCH_UP        12          58                   58                            0
WRITE_COMPLETION      8           15                   15                            0
WRITELOG              3           9                    9                             0
PAGELATCH_UP          2           4                    4                             0
SOS_SCHEDULER_YIELD   32277       1                    0                             1
IO_COMPLETION         8           0                    0                             0
LATCH_SH              3           0                    0                             0
LOGBUFFER             1           0                    0                             0

Вы можете видеть, что зарегистрировано 68 секунд NETWORK_IO. Но поскольку цикл вставки является однопоточным действием, которое занимает 36 секунд, этого не может быть. (Да, используются несколько потоков, но операции являются последовательными, а не параллельными, поэтому вы не можете накапливать больше времени ожидания, чем общая продолжительность запроса)

Если я не использую расширенные события, а только DMVs статистики ожидания в тихом экземпляре (только я запускаю вставку), я получаю это:

Wait Type                   Wait Count  Total Wait Time (ms)  Total Resource Wait Time (ms) Signal Resource Wait Time (ms)
SOS_SCHEDULER_YIELD             8873                 0.21                                    0.01                                    0.20
PAGEIOLATCH_UP                  3                    0.02                                    0.02                                    0.00
PREEMPTIVE_OS_AUTHENTICATIONOPS 17                   0.02                                    0.02                                    0.00
PAGEIOLATCH_SH                  1                    0.00                                    0.00                                    0.00

Таким образом, NETWORK_IO, который вы видели в расширенном журнале событий, не был связан с вашим циклом вставки. (Если вы не включите nocount, у вас будет массовый асинхронный ввод-вывод в сети, +1 Мартин)

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

Эдвард Дортланд
источник
1
«Вы достигаете предела производительности, не видя узких мест в ресурсах, потому что вы достигли предела того, что можно обрабатывать в одном потоке одной сессии»: вы описываете узкое место в 100% ЦП (на одном ядре). Если узкого места нет, система будет работать быстрее, поэтому должно быть что-то еще.
Ремус Русану
Ваш ответ очень информативен, Эдвард. Похоже, что параллелизм - это решение нашей проблемы, над которой мы уже работаем, хотя это требует изменений в структуре нашей базы данных. Однако, как и Ремус, мне все еще любопытно, почему машина не использует все (одного) процессора или дисковые ресурсы.
Djof
9

Как правило, вы начинаете с рассмотрения sys.dm_exec_requests, в частности wait_time, wait_typeи wait_resourceваших запросов INSERT. Это даст четкое представление о том, что блокирует вашу вставку. Результаты покажут, является ли конфликт блокировкой, событиями роста файла, ожиданием сброса журнала, конфликтом выделения (проявляется как конфликт блокировки страницы PFS) и т. Д. И т. Д. И т. Д. И т. Д. И т. Д. После измерения обновите свой вопрос соответствующим образом. Я настоятельно призываю вас остановиться сейчас и прочитать методологию устранения неполадок, связанных с ожиданием и очередями, прежде чем продолжить.

Ремус Русану
источник
3

Я запустил тестовый скрипт на странице, связанной в OP, с BEGIN TRAN / COMMIT вокруг цикла. На моей машине это заняло 1:28, чтобы закончить в первый раз.

Затем я переместил эти две команды за пределы цикла:

SELECT @Random = ROUND(((@Upper - @Lower -1) * RAND() + @Lower), 0)
SET @InsertDate = DATEADD(dd, @Random, GETDATE())

Это закончилось через 28 секунд после этого.

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

Кстати, твердотельные накопители не всегда являются лучшей технологией для приложений с интенсивной записью. Для лучшей производительности убедитесь, что ваш журнал БД находится на другой букве диска, чем данные БД, файл журнала предварительно увеличен до максимального размера и никогда не обрезайте журнал.

RickNZ
источник
Спасибо за ваш вклад RickNZ. Я не получил более быстрых результатов, переместив код из цикла. Подождите, я заметил, что если вы запустите его несколько раз, он станет быстрее, это может быть то, что вы испытали. Я знаю, что твердотельные накопители - это не серебряные пули, но я все еще чувствую, что производительность не такая, как могла бы быть
Djof
1

Другой DMV, который я использую для определения медлительности, это sys.dm_os_waiting_tasks . Если ваш запрос не загружает процессор, вы можете найти дополнительную информацию об ожиданиях от этого DMV.

StanleyJohns
источник
0

Я проверяю список событий ожидания для SQL 2008 и не вижу NETWORK_IO в списке: http://technet.microsoft.com/en-us/library/ms179984(v=sql.100).aspx

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

Что касается ожидания сети, то это может произойти, даже если вы работаете на автономном сервере. Вы проверили настройки для своих сетевых карт? Мне интересно, если они являются проблемой.

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

SQLRockstar
источник
1
NETWORK_IOПоказано , потому что OP использует расширенные события. Это никогда не обновлялось вsys.dm_xe_map_values
Мартин Смит
Я думаю о том же SQLRockstar, только то, что может происходить. Я пытался полностью отключить сетевые карты. Мартин указал, что некоторые старые файлы могут быть все еще там, и я обновлю результаты завтра, чтобы посмотреть, изменит ли это что-нибудь.
Djof
Кроме того, это могло бы помочь, если бы мы могли видеть планы выполнения для операторов.
SQLRockstar