Да, это звучит как очень общая проблема, но я пока не смог ее сузить.
Итак, у меня есть оператор UPDATE в пакетном файле sql:
UPDATE A
SET A.X = B.X
FROM A JOIN B ON A.B_ID = B.ID
B имеет 40k записей, A имеет 4M записей, и они связаны 1-к-n через A.B_ID, хотя между ними нет FK.
В общем, я предварительно вычисляю поле для целей интеллектуального анализа данных. Хотя я изменил название таблиц для этого вопроса, я не изменил формулировку, это действительно так просто.
Это занимает несколько часов, поэтому я решил все отменить. БД была повреждена, поэтому я удалил ее, восстановил резервную копию, которую я сделал непосредственно перед запуском оператора, и решил перейти к деталям с курсором:
DECLARE CursorB CURSOR FOR SELECT ID FROM B ORDER BY ID DESC -- Descending order
OPEN CursorB
DECLARE @Id INT
FETCH NEXT FROM CursorB INTO @Id
WHILE @@FETCH_STATUS = 0
BEGIN
DECLARE @Msg VARCHAR(50) = 'Updating A for B_ID=' + CONVERT(VARCHAR(10), @Id)
RAISERROR(@Msg, 10, 1) WITH NOWAIT
UPDATE A
SET A.X = B.X
FROM A JOIN B ON A.B_ID = B.ID
WHERE B.ID = @Id
FETCH NEXT FROM CursorB INTO @Id
END
Теперь я вижу, что он работает с сообщением с идентификатором по убыванию. Что происходит, это занимает около 5 минут, чтобы перейти от id = 40k к id = 13
А потом в id 13 почему-то, кажется, зависает. БД не имеет к ней никакого подключения, кроме SSMS, но на самом деле она не зависла:
- жесткий диск работает постоянно, поэтому он определенно что-то делает (я проверил в Process Explorer, что это действительно процесс sqlserver.exe, использующий его)
Я запустил sp_who2, нашел SPID (70) сеанса SUSPENDED и запустил следующий скрипт:
выберите * из sys.dm_exec_requests r присоединитесь к sys.dm_os_tasks t на r.session_id = t.session_id где r.session_id = 70
Это дает мне wait_type, который в большинстве случаев равен PAGEIOLATCH_SH, но на самом деле иногда меняется на WRITE_COMPLETION, что, как мне кажется, происходит, когда он очищает журнал
- файл журнала, который был 1,6 ГБ, когда я восстановил БД (и когда он получил идентификатор 13), теперь 3,5 ГБ
Другая, может быть, полезная информация:
- количество записей в таблице A для B_ID 13 невелико (14)
- У моей коллеги нет той же проблемы на ее машине, с копией этой БД (от пары месяцев назад) с той же структурой.
- таблица A является безусловно самой большой таблицей в БД
- Он имеет несколько индексов, и несколько индексированных представлений используют его.
- В БД нет другого пользователя, он локальный и ни одно приложение не использует его.
- Файл LDF не ограничен в размере.
- Модель восстановления ПРОСТА, уровень совместимости 100
- Procmon не дает мне много информации: sqlserver.exe много читает и пишет из файлов MDF и LDF.
Я все еще жду, пока он закончится (это было 1:30), но я надеялся, что, возможно, кто-то даст мне какое-то другое действие, я мог бы попытаться устранить это.
Отредактировано: добавление выдержки из журнала procmon
15:24:02.0506105 sqlservr.exe 1760 ReadFile C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TA.mdf SUCCESS Offset: 5,498,732,544, Length: 8,192, I/O Flags: Non-cached, Priority: Normal
15:24:02.0874427 sqlservr.exe 1760 WriteFile C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TA.mdf SUCCESS Offset: 6,225,805,312, Length: 16,384, I/O Flags: Non-cached, Write Through, Priority: Normal
15:24:02.0884897 sqlservr.exe 1760 WriteFile C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TA_1.LDF SUCCESS Offset: 4,589,289,472, Length: 8,388,608, I/O Flags: Non-cached, Write Through, Priority: Normal
При использовании DBCC PAGE кажется, что чтение и запись в поля выглядят как таблицы A (или один из ее индексов), но для другого B_ID это 13. Может быть, перестроение индексов?
Отредактировано 2: план выполнения
Поэтому я отменил запрос (фактически удалил базу данных и ее файлы, а затем восстановил ее) и проверил план выполнения:
UPDATE A
SET A.X = B.X
FROM A JOIN B ON A.B_ID = B.ID
WHERE B.ID = 13
(Предполагаемый) план выполнения такой же, как и для любого B.ID, и выглядит довольно просто. Предложение WHERE использует поиск индекса по некластеризованному индексу B, JOIN использует поиск кластеризованного индекса по обоим PK таблиц. Поиск кластеризованного индекса в A использует параллелизм (x7) и представляет 90% процессорного времени.
Что еще более важно, на самом деле выполнение запроса с ID 13 происходит немедленно.
Отредактировано 3: фрагментация индекса
Структура индексов следующая:
B имеет один кластеризованный PK (не поле идентификатора) и один некластеризованный уникальный индекс, первое поле которого - B.ID - этот второй индекс, кажется, используется всегда.
А имеет один кластерный ПК (поле не связано).
Есть также 7 представлений об A (все включают поле AX), каждое с собственным кластеризованным PK, а также другой индекс, который также включает поле AX
Представления фильтруются (с полями, не входящими в это уравнение), поэтому я сомневаюсь, что ОБНОВЛЕНИЕ А каким-либо образом будет использовать сами представления. Но у них есть индекс, включающий AX, поэтому изменение AX означает запись 7 представлений и 7 индексов, которые включают поле.
Хотя ожидается, что ОБНОВЛЕНИЕ будет медленнее, нет никаких причин, по которым конкретный идентификатор будет намного длиннее, чем другие.
Я проверил фрагментацию для всех индексов, все были на уровне <0,1%, кроме вторичных индексов просмотров , все между 25% и 50%. Коэффициенты заполнения для всех индексов, кажется, в порядке, между 90% и 95%.
Я реорганизовал все вторичные индексы и перезапустил свой скрипт.
Это все еще повешено, но в другой точке:
...
(0 row(s) affected)
Updating A for B_ID=14
(4 row(s) affected)
Тогда как ранее журнал сообщений выглядел так:
...
(0 row(s) affected)
Updating A for B_ID=14
(4 row(s) affected)
Updating A for B_ID=13
Это странно, потому что это означает, что он даже не висит в одной и той же точке WHILE
цикла. Остальное выглядит одинаково: та же самая строка UPDATE, ожидающая в sp_who2, та же самая ожидающая PAGEIOLATCH_EX и то же самое интенсивное использование HD от sqlserver.exe.
Следующий шаг - удалить все индексы и представления и воссоздать их, я думаю.
Отредактировано 4: удаление, а затем перестроение индексов
Итак, я удалил все проиндексированные представления, которые у меня были в таблице (7 из них, 2 индекса на представление, включая кластерное). Я запустил исходный скрипт (без курсора), и он фактически запустился за 5 минут.
Поэтому моя проблема связана с существованием этих индексов.
Я обновил свои индексы после запуска обновления, и это заняло 16 минут.
Теперь я понимаю, что для восстановления индексов требуется время, и я на самом деле в порядке с полной задачей, занимающей 20 минут.
Я до сих пор не понимаю, почему, когда я запускаю обновление, не удаляя сначала индексы, это занимает несколько часов, но когда я сначала удаляю их, а затем воссоздаю, это занимает 20 минут. Разве это не должно занять примерно одинаковое время в любом случае?
DBCC PAGE
чтобы увидеть, что пишется.Ответы:
Изменить: Поскольку я не могу комментировать ваш исходный пост, я отвечу здесь на ваш вопрос из Edit 4. У вас есть 7 индексов на AX Index является B-деревом , и каждое обновление в этом поле вызывает перебалансировку B-дерева. Это быстрее восстановить эти индексы с нуля, чем каждый раз перебалансировать его.
источник
Во время этого процесса нужно обратить внимание на системные ресурсы (память, диск, процессор). Я попытался вставить 7 миллионов отдельных строк в одну таблицу за одну большую работу, и мой сервер завис, как у вас.
Оказывается, на моем сервере недостаточно памяти для выполнения этого задания массовой вставки. В подобных ситуациях SQL любит удерживать память и не отпускать ее ... даже после того, как указанная команда вставки может или не может быть выполнена. Чем больше команд обрабатывается в больших заданиях, тем больше памяти израсходовано. Быстрая перезагрузка освободила указанную память.
То, что я хотел бы сделать, это запустить этот процесс с нуля с запущенным диспетчером задач. Если использование памяти превышает 75%, шансы того, что ваша система / процессы астрономически замерзнут.
Если ваша память / ресурсы действительно ограничены, как отмечено выше, тогда вы можете разделить процесс на более мелкие части (с периодической перезагрузкой, если использование памяти велико) вместо одной большой работы или обновить до 64-битного сервера с большим объемом памяти.
источник
Сценарий обновления всегда быстрее, чем при использовании процедуры.
Поскольку вы обновляете столбец X всех строк в таблице A, убедитесь, что сначала вы удалили индекс для этой строки. Также убедитесь, что в этом столбце нет активных триггеров и ограничений.
Обновление индексов является дорогостоящим делом, равно как и проверка ограничений и выполнение триггеров на уровне строк, которые выполняют поиск в других данных.
источник