2010-11-14 4 views
1

Я попытался выполнить следующий запрос:Можно ли оптимизировать этот запрос?

update ms 
    set user_B_total_duration = amc.total_duration 
    from monthly_statistics ms 
     inner join aggregate_monthly_conversations amc 
      on ms.user_B = amc.user_B 

но запрос был выполнение более 10 часов. В каждой таблице содержится около 23M записей (ежемесячные_статистики и aggregate_monthly_conversations). Механизм базы данных - это SQL Server 2008, а ПК - четырехъядерный процессор 2,66 ГГц, 4 ГБ оперативной памяти.

Кто-нибудь знает, можно ли оптимизировать запрос выше или выполнить одну и ту же задачу любым способом?

+2

Можете ли вы показать нам структуры таблиц и рассказать нам, какие индексы у вас уже есть? –

+1

Также был бы полезен план выполнения. – Donnie

+0

Я бы, но вы не принимаете ответы. – smirkingman

ответ

1

Если я устранении неполадок это, это то, что я бы в поисках:

  1. Если практичны, убедитесь никто не монополизирует таблицу (то есть, запирая его)
  2. Убедитесь столбцы объединения индексируются (т.е., ms.user_B, amc.user_B)
  3. Обновление столбцов в пакетах с использованием UPDATE TOP (100) ms SET ...

Пункт 3 на самом деле очень важно при создании больших вставки/обновления/удаления. SQL Server генерирует журнал, чтобы отменить эту операцию, если она не работает частично, и это становится все дороже. Если вам нужно обновить ряды шириной 1 м, это может быть намного быстрее для работы по 20 рядов по 50 тыс. Строк. Я видел советы, утверждающие, что это имеет существенное различие (и это AFAICT). Кроме того, это предотвращает очередность запросов к таблице.

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

Таким образом, в вашем случае, может быть:

declare @update_date datetime; 
set @update_date = getdate(); 

while 1 = 1 
begin 
    update top(10000) ms set 
     user_B_total_duration = amc.total_duration, 
     last_updated = @update_date 
    from 
     monthly_statistics ms 
     inner join aggregate_monthly_conversations amc 
     on ms.user_B = amc.user_B 
    where 
     ms.last_updated < @update_date; 

    if @@rowcount = 0 break; 
end 

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

+0

Спасибо Мэтту! Одна вещь, которую я хотел бы отметить здесь: сначала я попытался обновить 10000 записей в то время, и это заняло около 7 минут. Затем я попытался обновить 1000000 записей в то время, и потребовалось около 20 секунд. Еще один интересный вопрос: как выбрать оптимальное число :) –

+0

@niko, я надеялся, что вы опубликуете полученное время - спасибо! Я ожидал, что это займет ~ 1-2 м, поэтому я впечатлен вашими результатами. Я согласен, что это будет интересный вопрос. Я хотел бы знать, есть ли вычислительная связь между размером набора изменений и размером буфера/ОЗУ, загрузкой и т. Д. – Matt

0

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

+0

Что случилось с присоединениями? Запись 23M записей в таблицу «ad hoc» будет v. Дорогой – spender

+1

Насколько я понимаю, этот запрос является одноразовой модификацией данных. Поэтому добавление индексов также будет очень дорогостоящим. У конкретной таблицы может быть преимущество не блокировать таблицы во время вставки, а затем получить более короткое время блокировки во время обновления. Вопрос: сколько строк возвращает соединение? –

0

Индексы monthly_statistics.User_B и aggregate_monthly_conversations.User_B будет отличным началом, возможно includingtotal_duration по индексу aggregate_monthly_conversations.User_B.

0

Похоже, что вы полностью освободили память на этом компьютере, и SQL Server переводит память на диск. 4GB Ram - это не так много.

Сколько времени занимает этот запрос? Сколько строк возвращается?

select 
'update monthly_statistics set user_B = ' + 
CAST(amc.total_duration as varchar) + ' ' + 
'where user_B = ' + 
CAST(ms.user_B as varchar) + ' GO' 
from monthly_statistics ms 
inner join aggregate_monthly_conversations amc 
on ms.user_B = amc.user_B 

Вы можете использовать этот выход для обновления таблицы.

0

Более общий вопрос: почему вы вообще занимаетесь в первую очередь этой дорогостоящей обработкой пакетной обработки в конце месяца, когда вы можете получить полную_коду любого пользователя с помощью специального запроса? Что такое обоснование для принятия этого подхода, не являющегося РСУБД? Обычно люди прибегают к пакетной обработке данных на конец месяца, когда специальный запрос слишком дорог и медленный для специальных целей отчетности. Это так в вашем случае?

С индексами в соединенных столбцах, ms.user_b и amc.user_b, вы должны иметь возможность получить полное_количество пользователя с простым объединением двух таблиц. Сколько различных пользователей можно найти среди записей 23M? Если столбец ms.user_b имеет низкую мощность, возможно, составной индекс, такой как (ms.user_b, timeperiod) или что-то сравнимое (мы не знаем вашу схему), обеспечит требуемую производительность ad hoc без неприемлемого снижения производительности при вставках/обновлениях ?

Если вы должны оставить вещи, как они есть, вы можете попробовать хранимую процедуру, в которой Вы выбираете определенный набор AMC.user_ids в курсор, и обработать обновление таблицы MS одного идентификатора в то время:

... 
from monthly_statistics ms 
inner join aggregate_monthly_conversations amc 
on ms.user_B = amc.user_B and ms.user_b = @currentuserid 

Для этого также потребуется хотя бы один простой индекс: по столбцу ms.user_b или составной индекс (ms.user_b, {some other column (s)}).

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