Я использую модуль mysql2 в nodejs v8.9.4.
Это моя функция для получения сообщения из очереди сообщений, которое удовлетворяет этим условиям:
status==0
если количество botId с status==1 меньше 10
если retry_after в таблице wait для botId+chatId и просто botId меньше, чем NOW (временная метка)
если нет такого же chatId с status==1
static async Find(activeMessageIds, maxActiveMsgPerBot) {
let params = [maxActiveMsgPerBot];
let filterActiveMessageIds = ' ';
let time = Util.GetTimeStamp();
if (activeMessageIds && activeMessageIds.length) {
filterActiveMessageIds = 'q.id NOT IN (?) AND ';
params.push(activeMessageIds);
}
let q =
`select q.*
from bot_message_queue q
left join bot_message_queue_wait w on q.botId=w.botId AND q.chatId=w.chatId
left join bot_message_queue_wait w2 on q.botId=w2.botId AND w2.chatId=0
where
q.status=0 AND
q.botId NOT IN (select q2.botId from bot_message_queue q2 where q2.status=1 group by q2.botId HAVING COUNT(q2.botId)>?) AND
${filterActiveMessageIds}
q.chatId NOT IN (select q3.chatId from bot_message_queue q3 where q3.status=1 group by q3.chatId) AND
(w.retry_after IS NULL OR w.retry_after <= ?) AND
(w2.retry_after IS NULL OR w2.retry_after <= ?)
order by q.priority DESC,q.id ASC
limit 1;`;
params.push(time);
params.push(time);
let con = await DB.connection();
let result = await DB.query(q, params, con);
if (result && result.length) {
result = result[0];
let updateQ = `update bot_message_queue set status=1 where id=?;`;
await DB.query(updateQ, [result.id], con);
} else
result = null;
con.release();
return result;
}
Этот запрос отлично работает в моей локальной системе разработки. Он также отлично работает на серверах phpmyadmin за пару миллисекунд.
НО, когда он запускает throw nodejs + mysql2, использование процессора достигает 100% В этой таблице всего 2К строк.
CREATE TABLE IF NOT EXISTS `bot_message_queue` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`botId` int(10) UNSIGNED NOT NULL,
`chatId` varchar(50) CHARACTER SET utf8 NOT NULL,
`type` varchar(50) DEFAULT NULL,
`message` longtext NOT NULL,
`add_date` int(10) UNSIGNED NOT NULL,
`status` tinyint(2) UNSIGNED NOT NULL DEFAULT '0' COMMENT '0=waiting,1=sendig,2=sent,3=error',
`priority` tinyint(1) UNSIGNED NOT NULL DEFAULT '5' COMMENT '5=normal messages,<5 = bulk messages',
`delay_after` int(10) UNSIGNED NOT NULL DEFAULT '1000',
`send_date` int(10) UNSIGNED DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `botId` (`botId`,`status`),
KEY `botId_2` (`botId`,`chatId`,`status`,`priority`),
KEY `chatId` (`chatId`,`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `bot_message_queue_wait` (
`botId` int(10) UNSIGNED NOT NULL,
`chatId` varchar(50) CHARACTER SET utf8 NOT NULL,
`retry_after` int(10) UNSIGNED NOT NULL,
PRIMARY KEY (`botId`,`chatId`),
KEY `retry_after` (`retry_after`),
KEY `botId` (`botId`,`chatId`,`retry_after`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ОБНОВЛЕНИЕ: Настоящие табличные данные здесь
ОБНОВЛЕНИЕ 2:
FetchMessageTime:
- Мин .: 1788 мс
- Макс: 44285 мс
- Среднее значение: 20185,4 мс
До вчерашнего дня максимум был примерно 20 мс :( сейчас 40 секунд !!!
ОБНОВЛЕНИЕ 3: я объединил эти 2 соединения и где:
left join bot_message_queue_wait w on q.botId=w.botId AND q.chatId=w.chatId
left join bot_message_queue_wait w2 on q.botId=w2.botId AND w2.chatId=0
(w.retry_after IS NULL OR w.retry_after <= ?) AND
(w2.retry_after IS NULL OR w2.retry_after <= ?)
в один, надеюсь, это сработает так, как задумано!
left join bot_message_queue_wait w on q.botId=w.botId AND ( q.chatId=w.chatId OR w.chatId=0 )
и на данный момент я удалил 2 места, и время запроса вернулось к норме.
q.botId NOT IN (select ...)
q.chatId NOT IN (select ...)
Итак, эти два запроса, где запросы являются узкими местами, должны быть исправлены.
@JitendraSoftgrid drive.google.com/open?id=10gkrTA-csKSJuSQ7ymXwQ8Fxz08FZj1Y






NOT IN ( SELECT ... ) сложно оптимизировать.
OR не может быть оптимизирован.
В ORDER BY смешивание DESC и ASC исключает использование индекса (до 8.0). Рассмотрите возможность замены ASC на DESC. После этого поможет INDEX(priority, id)мощь.
Что такое ${filterActiveMessageIds}?
GROUP BY не нужен в
NOT IN ( SELECT q3.chatId
from bot_message_queue q3
where q3.status=1
group by q3.chatId )
INDEX(status, chatid)в этом порядке выиграет от этого подзапроса.
INDEX(status, botid)в этом порядке
Подробнее о создании индекса: http://mysql.rjweb.org/doc.php/index_cookbook_mysql
Итак, чтобы оптимизировать запрос, сначала добавьте эти индексы:
ALTER TABLE `bot_message_queue` ADD INDEX `bot_message_queue_idx_status_botid_chatid_priori_id` (`status`,`botId`,`chatId`,`priority`,`id`);
ALTER TABLE `bot_message_queue` ADD INDEX `bot_message_queue_idx_priority_id` (`priority`,`id`);
ALTER TABLE `bot_message_queue` ADD INDEX `bot_message_queue_idx_botid_status` (`botId`,`status`);
ALTER TABLE `bot_message_queue` ADD INDEX `bot_message_queue_idx_chatid_status` (`chatId`,`status`);
ALTER TABLE `bot_message_queue_wait` ADD INDEX `bot_message_queue_wa_idx_chatid_botid` (`chatId`,`botId`);
Теперь вы можете попробовать запустить этот запрос (обратите внимание, что я изменил порядок на все DESC, поэтому вы можете изменить его на ASC, если это необходимо):
SELECT
bot_message_queue.*
FROM
bot_message_queue q
LEFT JOIN
bot_message_queue_wait w
ON q.botId = w.botId
AND q.chatId = w.chatId
LEFT JOIN
bot_message_queue_wait w2
ON q.botId = w2.botId
AND w2.chatId = 0
WHERE
q.status = 0
AND NOT EXISTS (
SELECT
1
FROM
bot_message_queue AS q21
WHERE
q21.status = 1
AND q.botId = q21.botId
GROUP BY
q21.botId
HAVING
COUNT(q21.botId) > ?
ORDER BY
NULL
)
AND NOT EXISTS (
SELECT
1
FROM
bot_message_queue AS q32
WHERE
q32.status = 1
AND q.chatId = q32.chatId
GROUP BY
q32.chatId
ORDER BY
NULL
)
AND (
w.retry_after IS NULL
OR w.retry_after <= ?
)
AND (
w2.retry_after IS NULL
OR w2.retry_after <= ?
)
ORDER BY
q.priority DESC,
q.id DESC LIMIT 1
не может использовать Inner Join, потому что не все строки в T1 имеют соответствующую строку в T2, Inner Join проигнорирует эти строки, но это не то, что я хочу
@Exlord, извини, с первого взгляда не заметил этих условий. Я изменил рекомендации по индексации и запросам. Пожалуйста, попробуйте еще раз и оставьте отзыв.
Сначала мне нужны самые старые записи, поэтому q.id DESC не работает, это должен быть ASC. Ответ: подзапросы NOT EXIST не будут работать для меня, потому что подзапрос предназначен для фильтрации основного запроса на основе его возвращаемых значений, это не может быть простой true/false
@Exlord, не стесняйтесь менять его на ASC. Что касается частей NOT EXISTS - эта альтернатива эквивалентна операторам NOT IN в исходном запросе, поскольку я добавил соответствующие коррелированные условия внутри подзапросов. Почему это не сработает? Разве запрос не возвращает те же результаты?
Было бы намного проще помочь, если бы у этого
DDLбыли какие-то тестовые данные.