#mysql #group-by #sql-order-by
#mysql #группировка по #sql-order-by
Вопрос:
Итак, мне была поставлена задача воспроизвести функциональность, которую мы в настоящее время обрабатываем с помощью кода, в MySQL.
Приведенный ниже запрос работает прекрасно, возвращая 245 000 строк за 40 мс, однако, как только вы касаетесь его с помощью группы или порядка, он занимает более 6 с.
Есть ли у кого-нибудь какие-либо предложения о том, какие изменения необходимо внести в индексы или, возможно, как изменить запрос, чтобы улучшить его?
Спасибо
Без какой-либо группировки или упорядочения
select
s.id as sensorid,
s.sensortypeid,
COALESCE(s.pulserate, 1) as pulserate,
COALESCE(s.correctionFactor, 1) as correctionFactor,
ur.id as unitrateid,
COALESCE(ur.priceperkwh, 0) as priceperkwh,
COALESCE(ur.duosCharges, 0) as duosCharges,
IF(t.blnnonunitratecharges, t.nonunitratecharge/48, 0) as nonunitratecost,
IF(t.blnFeedIn, COALESCE(t.feedInRate, 0), 0) as feedInRate,
IF(t.blnRoc, COALESCE(t.rocRate, 0), 0) as rocRate,
from_unixtime(FLOOR(UNIX_TIMESTAMP(srs.dateTimeStamp)/(30*60))*(30*60)) as timeKey
from sensorreadings srs
inner join sensorpoints sp on (sp.id = srs.sensorpointid)
inner join sensors s on (s.id = sp.sensorid)
left join unitrates ur on ur.id = (
select
ur.id
from unitrates ur, tariffs t, companyhubs ch
where
ur.tariffid = t.id and
t.companyid = ch.companyid and
ch.hubid = s.hubid and
t.utilitytypeid = s.utilitytypeid and
(srs.dateTimeStamp between t.startdate and t.enddate) and
((time(srs.dateTimeStamp) between ur.starttime and ur.endtime) and
(ur.dayMask amp; POW(2, WEEKDAY(srs.dateTimeStamp)) <> 0) and
(ur.monthMask amp; POW(2, MONTH(srs.dateTimeStamp) - 1) <> 0))
order by
t.startdate desc,
ur.starttime desc
limit 0, 1
)
left join tariffs t on (t.id = ur.tariffid)
where
s.id = 5289
С группировкой и упорядочением
select
s.id as sensorid,
s.sensortypeid,
COALESCE(s.pulserate, 1) as pulserate,
COALESCE(s.correctionFactor, 1) as correctionFactor,
ur.id as unitrateid,
COALESCE(ur.priceperkwh, 0) as priceperkwh,
COALESCE(ur.duosCharges, 0) as duosCharges,
IF(t.blnnonunitratecharges, t.nonunitratecharge/48, 0) as nonunitratecost,
IF(t.blnFeedIn, COALESCE(t.feedInRate, 0), 0) as feedInRate,
IF(t.blnRoc, COALESCE(t.rocRate, 0), 0) as rocRate,
min(srs.reading) as minReading,
avg(srs.reading) as avgReading,
from_unixtime(FLOOR(UNIX_TIMESTAMP(srs.dateTimeStamp)/(30*60))*(30*60)) as timeKey
from sensorreadings srs
inner join sensorpoints sp on (sp.id = srs.sensorpointid)
inner join sensors s on (s.id = sp.sensorid)
left join unitrates ur on ur.id = (
select
ur.id
from unitrates ur, tariffs t, companyhubs ch
where
ur.tariffid = t.id and
t.companyid = ch.companyid and
ch.hubid = s.hubid and
t.utilitytypeid = s.utilitytypeid and
(srs.dateTimeStamp between t.startdate and t.enddate) and
((time(srs.dateTimeStamp) between ur.starttime and ur.endtime) and
(ur.dayMask amp; POW(2, WEEKDAY(srs.dateTimeStamp)) <> 0) and
(ur.monthMask amp; POW(2, MONTH(srs.dateTimeStamp) - 1) <> 0))
order by
t.startdate desc,
ur.starttime desc
limit 0, 1
)
left join tariffs t on (t.id = ur.tariffid)
where
s.id = 5289
group by timeKey
order by timeKey desc
Схемы
CREATE TABLE `sensorreadings` (
`sensorpointid` int(11) NOT NULL DEFAULT '0',
`reading` decimal(15,5) NOT NULL,
`dateTimeStamp` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`sensorpointid`,`dateTimeStamp`),
KEY `sensormetricid` (`sensormetricid`),
KEY `sensorreadings_timestamp` (`dateTimeStamp`,`sensorpointid`),
KEY `sensorpointid` (`sensorpointid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `sensorpoints` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`sensorid` int(11) DEFAULT NULL,
`hubpointid` int(11) DEFAULT NULL,
`pointlabel` varchar(255) NOT NULL,
`pointhash` varchar(255) NOT NULL,
`target` decimal(10,0) DEFAULT NULL,
`tolerance` decimal(10,0) DEFAULT '0',
`blnlivepoint` int(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `FK_sensorpoints_sensors` (`sensorid`),
CONSTRAINT `FK_sensorpoints_sensors` FOREIGN KEY (`sensorid`) REFERENCES `sensors` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=8824 DEFAULT CHARSET=latin1;
CREATE TABLE `sensors` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`hubid` int(11) DEFAULT NULL,
`sensortypeid` int(11) NOT NULL DEFAULT '5',
`pulserate` decimal(10,6) DEFAULT NULL,
`utilitytypeid` int(11) NOT NULL DEFAULT '1',
`correctionfactor` decimal(10,3) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FK_sensors_sensortypes` (`sensortypeid`),
KEY `FK_sensors_hubs` (`hubid`),
KEY `FK_sensors_utilitytypes` (`utilitytypeid`),
CONSTRAINT `FK_sensors_hubs` FOREIGN KEY (`hubid`) REFERENCES `hubs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `FK_sensors_sensortypes` FOREIGN KEY (`sensortypeid`) REFERENCES `sensortypes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
) ENGINE=InnoDB AUTO_INCREMENT=5503 DEFAULT CHARSET=latin1;
CREATE TABLE `tariffs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`companyid` int(11) DEFAULT NULL,
`utilitytypeid` int(11) DEFAULT NULL,
`startdate` date NOT NULL,
`enddate` date NOT NULL,
`blnnonunitratecharges` int(1) DEFAULT '0',
`nonunitratecharge` decimal(16,8) DEFAULT '0.00000000',
`blnFeedIn` int(1) DEFAULT '0',
`blnRoc` int(1) DEFAULT '0',
`rocRate` decimal(16,8) DEFAULT '0.00000000',
`feedInRate` decimal(16,8) DEFAULT '0.00000000',
PRIMARY KEY (`id`),
KEY `companyid` (`companyid`,`utilitytypeid`,`startdate`,`enddate`),
KEY `startdate` (`startdate`,`enddate`),
) ENGINE=InnoDB AUTO_INCREMENT=1107 DEFAULT CHARSET=latin1;
CREATE TABLE `unitrates` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`tariffid` int(11) NOT NULL,
`priceperkwh` decimal(16,8) NOT NULL,
`starttime` time NOT NULL,
`endtime` time NOT NULL,
`duoscharges` decimal(10,5) DEFAULT NULL,
`dayMask` int(11) DEFAULT '127',
`monthMask` int(11) DEFAULT '4095',
PRIMARY KEY (`id`),
KEY `FK_unitrates_tariffs` (`tariffid`),
KEY `times` (`starttime`,`endtime`),
KEY `masks` (`dayMask`,`monthMask`),
CONSTRAINT `FK_unitrates_tariffs` FOREIGN KEY (`tariffid`) REFERENCES `tariffs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
) ENGINE=InnoDB AUTO_INCREMENT=3104 DEFAULT CHARSET=latin1;
Объясняет
Без групп / упорядочения
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|----|--------------------|-------|--------|---------------------------------|-------------------------|---------|-------------------------------|------|----------------------------------------------|
| 1 | PRIMARY | s | const | PRIMARY | PRIMARY | 4 | const | 1 | NULL |
| 1 | PRIMARY | sp | ref | PRIMARY,FK_sensorpoints_sensors | FK_sensorpoints_sensors | 5 | const | 1 | Using index |
| 1 | PRIMARY | srs | ref | PRIMARY,sensorpointid | PRIMARY | 4 | dbnameprod.sp.id | 211 | Using index |
| 1 | PRIMARY | ur | eq_ref | PRIMARY | PRIMARY | 4 | func | 1 | Using where |
| 1 | PRIMARY | t | eq_ref | PRIMARY | PRIMARY | 4 | dbnameprod.ur.tariffid | 1 | NULL |
| 2 | DEPENDENT SUBQUERY | ch | ref | hubid | hubid | 5 | const | 1 | Using where; Using temporary; Using filesort |
| 2 | DEPENDENT SUBQUERY | t | ref | PRIMARY,companyid,startdate | companyid | 10 | dbnameprod.ch.companyid,const | 1 | Using where; Using index |
| 2 | DEPENDENT SUBQUERY | ur | ref | FK_unitrates_tariffs,times | FK_unitrates_tariffs | 4 | dbnameprod.t.id | 1 | Using where |
С упорядочением / группировкой
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|----|--------------------|-------|--------|---------------------------------------------------------------|-------------------------|---------|-------------------------------|------|----------------------------------------------|
| 1 | PRIMARY | s | const | PRIMARY | PRIMARY | 4 | const | 1 | Using temporary; Using filesort |
| 1 | PRIMARY | sp | ref | PRIMARY,FK_sensorpoints_sensors | FK_sensorpoints_sensors | 5 | const | 1 | Using index |
| 1 | PRIMARY | srs | ref | PRIMARY,sensormetricid,sensorreadings_timestamp,sensorpointid | PRIMARY | 4 | dbnameprod.sp.id | 211 | Using index |
| 1 | PRIMARY | ur | eq_ref | PRIMARY | PRIMARY | 4 | func | 1 | Using where |
| 1 | PRIMARY | t | eq_ref | PRIMARY | PRIMARY | 4 | dbnameprod.ur.tariffid | 1 | NULL |
| 2 | DEPENDENT SUBQUERY | ch | ref | hubid | hubid | 5 | const | 1 | Using where; Using temporary; Using filesort |
| 2 | DEPENDENT SUBQUERY | t | ref | PRIMARY,companyid,startdate | companyid | 10 | dbnameprod.ch.companyid,const | 1 | Using where; Using index |
| 2 | DEPENDENT SUBQUERY | ur | ref | FK_unitrates_tariffs,times | FK_unitrates_tariffs | 4 | dbnameprod.t.id | 1 | Using where |
Комментарии:
1. Вопросы производительности должны включать
EXPLAIN ANALYZE
и некоторую информацию о размере таблицы, индексе, текущей производительности, времени ожидания и т. Д.Slow
это относительный термин, и нам нужно реальное значение для сравнения. MySQL2. @JuanCarlosOropeza — Спасибо, что перезвонили мне. Я добавил
EXPLAIN
анализ — есть ли что-нибудь, что мне нужно добавить в OP?3. Продвигая использование explict
JOIN
sintaxis, Аарон Бертран написал об этом хорошую статью » Вредные привычки: использование объединений в старом стиле «.
Ответ №1:
Ну, вы группируете и упорядочиваете вычисляемое поле timeKey
, а в базе данных нет индекса для этого поля.
Поэтому db необходимо вычислить все строки перед выполнением group by, а затем выполнить упорядочение, и без index не может ускорить вычисления.
Предложение: создайте поле времени в своей базе данных и добавьте индекс для этого поля.
Комментарии:
1. Спасибо Хуан. Вычисляемое поле
timeKey
является динамическим (т. Е.(30*60)
Может меняться в зависимости от требований пользователя) — при выполнении некоторого тестирования удаление левого соединенияunitrates
значительно ускоряет процесс при группировании / упорядочении? Есть мысли?2. Аналогичная проблема. В этом соединении вы пытаетесь найти соответствие с подзапросом. И у этого нет предварительно вычисленного индекса. Но не имеет смысла, чтобы первый запрос выполнялся быстрее с тем же соединением. Я предлагаю вам сначала попытаться оптимизировать только подзапрос, потому что он также использует вычисляемое поле для создания объединений
Ответ №2:
Прежде чем рассматривать производительность, давайте обсудим вероятность того, что запрос нарушен.
При выполнении a GROUP BY
все неагрегированные SELECT
значения должны быть включены в GROUP BY
. В противном случае может быть доставлено любое случайное значение.
Кроме того, этот шаблон:
SELECT ..., AVG(a.x)
FROM a
JOIN b ON ...
GROUP BY a.id
обычно приводит к увеличению количества строк (из-за JOIN
) с последующим вычислением агрегатов по завышенному количеству строк. Добавьте COUNT(*)
, чтобы узнать, подхожу ли я для вашего случая. Потому COUNT
что ответ может быть явно неправильным; потому AVG
что он может быть слегка неправильным; потому MIN
что он, вероятно, правильный. И, наконец GROUP BY
, уменьшается количество строк.
Обычным способом лечения является вычисление агрегатов без JOINs
(я не уверен, возможно ли это в вашем случае). Может быть, что-то вроде…
...
JOIN (
SELECT min(srs.reading) as minReading,
avg(srs.reading) as avgReading,
from_unixtime(FLOOR(UNIX_TIMESTAMP(srs.dateTimeStamp)/
(30*60))*(30*60)) as timeKey
FROM srs
GROUP BY timeKey
) AS r
JOIN ...
Обычно «плохая» идея иметь date
и time
в отдельных столбцах. С DATETIME
or TIMESTAMP
легче сравнивать и т. Д. (Мне неясно, что вы делаете с вашей отдельной датой и временем.) Это также может быть проблемой производительности.
3 таблицы приводят к куче JOINing
, WHERE s.id = 5289
что затрудняет передачу в srs. Возможно, вам потребуется переосмыслить схему как еще одну проблему с производительностью.
Я понимаю, что значения разные, но могли бы
order by
t.startdate desc,
ur.starttime desc
заменяется на
order by srs.dateTimeStamp
Это может привести к тому, что мы сможем использовать индекс.
Я удивлен, что вы используете DECIMAL(m,n)
вместо FLOAT
для показаний датчиков.
Комментарии:
1. Спасибо, Рик, я совершенно забыл, что опубликовал это. В итоге я нашел решение, но в конечном итоге оно не подходит для того, на что я надеялся. После внесения некоторых изменений я смог вернуть 100 тыс. строк примерно за 400 мс, однако, если бы я захотел ограничить это только 10 строками, это все равно заняло бы 400 мс. Для отдельного времени / даты у нас есть тарифы, которые основаны на датах, а затем каждый тариф имеет переменные затраты в зависимости от дня, месяца и времени. Еще раз спасибо 🙂