Группа По часу по большому набору данных

12

Используя MS SQL 2008, я выбираю усредненное поле из 2,5 миллионов записей. Каждая запись представляет одну секунду. MyField - это среднечасовое значение этих 1-секундных записей. Конечно, процессор сервера достигает 100%, а выбор занимает слишком много времени. Возможно, мне нужно сохранить эти усредненные значения, чтобы в SQL не требовалось выбирать все эти записи в каждом запросе. Что можно сделать?

  SELECT DISTINCT
         CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR) AS TimeStampHour,
         MIN([timestamp]) as TimeStamp,
         AVG(MyField) As AvgField
    FROM MyData
   WHERE TimeStamp > '4/10/2011'
GROUP BY CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR)
ORDER BY TimeStamp

источник
6
Является ли TimeStamp частью кластерного индекса? Это должно быть ...
@antisanity - почему? он разгоняет процессор, а не диск io
Джек говорит попробуйте topanswers.xyz

Ответы:

5

Частью запроса является максимальная загрузка ЦП на длительные периоды - это функции в предложении GROUP BY и тот факт, что группирование всегда будет требовать неиндексированной сортировки в этом случае. Хотя индекс в поле отметки времени поможет начальному фильтру, эта операция должна выполняться для каждой строки, которой соответствует фильтр. Ускорение этого - использование более эффективного маршрута для выполнения той же работы, которая предложена Алексом, поможет, но у вас все еще есть огромная неэффективность, потому что любая комбинация функций, которую вы используете планировщиком запросов, не сможет придумать что-то, что будет помогать любому индексу, так что ему придется проходить через каждую строку, сначала запустив функции для вычисления значений группировки, только затем он сможет упорядочить данные и вычислить агрегаты по результирующим группировкам.

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

Вы можете сохранить дополнительный столбец для каждой строки, содержащей время, округленное до часа, и индексировать этот столбец для использования в таких запросах. Это денормализует ваши данные, поэтому может показаться «грязным», но это будет работать и будет чище, чем кэширование всех агрегатов для будущего использования (и обновление этого кэша по мере изменения базовых данных). Дополнительный столбец должен поддерживаться триггером или быть постоянным вычисляемым столбцом, а не поддерживаться логикой в ​​другом месте, поскольку это будет гарантировать все текущие и будущие места, в которые могут быть вставлены данные или обновлены столбцы меток времени или существующие строки приводят к согласованным данным в новом колонка. Вы все еще можете получить МИН (отметку времени). То, что запрос приведет таким образом, - это все-таки обход всех строк (этого, очевидно, избежать нельзя), но он может делать это в порядке индексации, вывод строки для каждой группировки при переходе к следующему значению в индексе, вместо того, чтобы запоминать весь набор строк для неиндексированной операции сортировки, прежде чем можно будет выполнить группирование / агрегирование. Он также будет использовать намного меньше памяти, поскольку ему не нужно будет запоминать строки из предыдущих значений группировки, чтобы обработать то, на что он смотрит сейчас, или остальные.

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

Метод, который неденормализуйте ваши данные, но все же требует дополнительной структуры, это использование «расписания», в данном случае одного, содержащего одну строку в час, на все время, которое вы, вероятно, рассмотрите. Эта таблица не будет занимать значительный объем пространства в БД или значительный размер - для покрытия временного промежутка в 100 лет таблица, содержащая одну строку из двух дат (начало и конец часа, например '2011-01-01 @ 00: 00: 00.0000 ',' 2011-01-01 @ 00: 00: 59.9997 ', "9997" - это наименьшее число миллисекунд, которое поле DATETIME не округляет до следующей секунды), которые являются частью Кластерный первичный ключ будет занимать ~ 14 Мбайт пространства (8 + 8 байт на строку * 24 часа / день * 365,25 дней / год * 100, плюс немного для издержек на структуру дерева кластерного индекса, но эти издержки не будут значительными) ,

SELECT CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR) AS TimeStampHour
     , MIN([timestamp]) as TimeStamp
     , AVG(MyField) As AvgField
FROM TimeRangeByHours tt
INNER JOIN MyData md ON md.TimeStamp BETWEEN tt.StartTime AND tt.EndTime
WHERE tt.StartTime > '4/10/2011'
GROUP BY tt.StartTime
ORDER BY tt.StartTime

Это означает, что планировщик запросов может организовать индекс для MyData.TimeStamp, который будет использоваться. Планировщик запросов должен быть достаточно ярким, чтобы понять, что он может шагать по таблице ручного управления в шаге с индексом MyData.TimeStamp, снова выводя по одной строке на группу и отбрасывая каждый набор или строки, когда он достигает следующего значения группировки. Нет необходимости хранить все промежуточные строки где-то в оперативной памяти, а затем выполнять неиндексированную сортировку. Конечно, этот метод требует, чтобы вы создали таблицу времени и удостоверились, что она охватывает достаточно далеко как вперед, так и назад, но вы можете использовать таблицу времени для запросов ко многим полям даты в разных запросах, где в качестве опции «дополнительный столбец» потребуется дополнительный вычисляемый столбец для каждого поля даты, которое необходимо отфильтровать / сгруппировать таким образом, и небольшой размер таблицы (если только он не нужен для охвата 10,

У метода таблицы времени есть дополнительная разница (которая может быть весьма выгодна) по сравнению с текущей ситуацией и решением для вычисляемых столбцов: он может возвращать строки за периоды, для которых нет данных, просто путем изменения INNER JOIN в примере запроса выше быть ЛЕВЫМ НАРУЖНЫМ.

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

Примечание: вам не нужно это предложение DISTINCT в исходном запросе. Группировка обеспечит, чтобы эти запросы возвращали только одну строку за рассматриваемый период, поэтому DISTINCT ничего не будет делать, только немного увеличит нагрузку на ЦП (если планировщик запросов не заметит, что отличное будет неактивным, в этом случае он будет игнорируйте его и не используйте дополнительное время процессора).

Дэвид Спиллетт
источник
3

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

  SELECT DISTINCT
         dateadd(hour,datediff(hour,0,[timestamp]),0) AS TimeStampHour,
         MIN([timestamp]) as TimeStamp,
         AVG(MyField) As AvgField
    FROM MyData
   WHERE TimeStamp > '4/10/2011'
GROUP BY dateadd(hour,datediff(hour,0,[timestamp],0);
ORDER BY TimeStamp
Hogan
источник
1

Вы хотите сделать запрос быстрее или спрашиваете, как сделать снимок данных и сохранить его?

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

select convert(varchar(13), getdate(), 121)

Если вам нужно сделать снимок и использовать его позже, используйте его insert intoдля создания новой таблицы с результатами вашего запроса. Индексируйте таблицу в соответствии и используйте ее. Из того, что я понимаю, вам понадобится индекс на TimeStampHour.

Также вы можете настроить работу, которая агрегирует ежедневные данные в вашей новой таблице агрегирования.

Алекс Аза
источник
-1

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


источник
1
-1 - нет, вы не «делаете это неиндексированным попаданием в каждую строку в базе данных» - любой индекс по- TimeStampпрежнему будет использоваться для фильтрации строк
Джек говорит, что попробуйте topanswers.xyz
-3

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

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

  1. Honeywell Uniformance PHD
  2. Osisoft PI
  3. Aspentech IP21
  4. и т.п.

Эти продукты могут хранить огромные объемы безумно плотных данных временных рядов (в проприетарных форматах), одновременно обеспечивая быструю обработку запросов на извлечение данных. Запросы могут указывать множество точек данных (также называемых тегами), длительные интервалы времени (месяцы / годы) и могут дополнительно выполнять самые разнообразные вычисления сводных данных (включая средние значения).

... и в целом: я всегда стараюсь избегать использования DISTINCTключевого слова при написании SQL. Это вряд ли хорошая идея. В вашем случае вы должны иметь возможность отказаться DISTINCTи получить те же результаты, добавив MIN([timestamp])к вашему GROUP BYпредложению.


источник
1
Это не совсем точно. Реляционная база данных отлично подходит для 2,5 миллионов записей. И он даже не делает соединения через множество таблиц. Первое указание на то, что вам нужно либо денормализовать свои данные, либо перейти на нереляционную систему, - это когда вы выполняете большие сложные соединения между многими таблицами. Набор данных автора фактически звучит как вполне приемлемое использование системы реляционных баз данных.