Оптимизирующий запрос Nodejs Mysql

Я использую модуль 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 ...)

Итак, эти два запроса, где запросы являются узкими местами, должны быть исправлены.

Было бы намного проще помочь, если бы у этого DDL были какие-то тестовые данные.

DEarTh 20.04.2018 10:59

@JitendraSoftgrid drive.google.com/open?id=10gkrTA-csKSJuSQ7ymXwQ8Fxz08FZj1Y

Exlord 20.04.2018 11:55
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
В последние годы архитектура микросервисов приобрела популярность как способ построения масштабируемых и гибких приложений. Laravel , популярный PHP...
Как построить CRUD-приложение в Laravel
Как построить CRUD-приложение в Laravel
Laravel - это популярный PHP-фреймворк, который позволяет быстро и легко создавать веб-приложения. Одной из наиболее распространенных задач в...
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
В предыдущем посте мы создали функциональность вставки и чтения для нашей динамической СУБД. В этом посте мы собираемся реализовать функции обновления...
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Роли и разрешения пользователей без пакета Laravel 9
Роли и разрешения пользователей без пакета Laravel 9
Этот пост изначально был опубликован на techsolutionstuff.com .
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
В предыдущей статье мы завершили установку базы данных, для тех, кто не знает.
0
2
643
2

Ответы 2

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

  1. В этом случае я бы заменил подзапрос NOT IN на NOT EXISTS, так как он может работать лучше.
  2. Переключите ORDER BY на все DESC или все ASC

Итак, чтобы оптимизировать запрос, сначала добавьте эти индексы:

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 07.05.2018 10:40

@Exlord, извини, с первого взгляда не заметил этих условий. Я изменил рекомендации по индексации и запросам. Пожалуйста, попробуйте еще раз и оставьте отзыв.

Tomer Shay 07.05.2018 15:40

Сначала мне нужны самые старые записи, поэтому q.id DESC не работает, это должен быть ASC. Ответ: подзапросы NOT EXIST не будут работать для меня, потому что подзапрос предназначен для фильтрации основного запроса на основе его возвращаемых значений, это не может быть простой true/false

Exlord 09.05.2018 07:43

@Exlord, не стесняйтесь менять его на ASC. Что касается частей NOT EXISTS - эта альтернатива эквивалентна операторам NOT IN в исходном запросе, поскольку я добавил соответствующие коррелированные условия внутри подзапросов. Почему это не сработает? Разве запрос не возвращает те же результаты?

Tomer Shay 09.05.2018 12:46

Другие вопросы по теме