Мой подзапрос добавляет 20 секунд ко времени выполнения. Как я могу ускорить это? - PullRequest
7 голосов
/ 25 сентября 2011

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

Отправлено 997 148 текстовых сообщений.

Я выполняю этот запрос:

SELECT
    m.id,
    m.user_id,
    m.api_key,
    m.to,
    m.message,
    m.sender_id,
    m.route,
    m.submission_reference,
    m.unique_submission_reference,
    m.reason_code,
    m.timestamp,
    d.id AS dlrid,
    d.dlr_status
FROM
    messages_sent m
LEFT JOIN
    delivery_receipts d
ON
    d.message_id = m.id
AND
    d.id = (SELECT MAX(id) FROM delivery_receipts WHERE message_id = m.id)

Возвращает 997 148 результатов, включая последний статус каждого сообщения.

Это займет 22,8688 секунд.

Вот SQL для messages_sent:

CREATE TABLE IF NOT EXISTS `messages_sent` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL,
`api_key` varchar(40) NOT NULL,
`to` varchar(15) NOT NULL,
`message` text NOT NULL,
`type` enum('sms','mms') NOT NULL DEFAULT 'sms',
`sender_id` varchar(15) NOT NULL,
`route` tinyint(1) unsigned NOT NULL,
`supplier` tinyint(1) unsigned NOT NULL,
`submission_reference` varchar(40) NOT NULL,
`unique_submission_reference` varchar(40) NOT NULL,
`reason_code` tinyint(1) unsigned NOT NULL,
`reason` text NOT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `api_key` (`api_key`),
KEY `sender_id` (`sender_id`),
KEY `route` (`route`),
KEY `submission_reference` (`submission_reference`),
KEY `reason_code` (`reason_code`),
KEY `timestamp` (`timestamp`),
KEY `to` (`to`),
KEY `unique_submission_reference` (`unique_submission_reference`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1000342 ;

А для delivery_receipts:

CREATE TABLE IF NOT EXISTS `delivery_receipts` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`message_id` int(10) unsigned NOT NULL,
`dlr_id` bigint(20) unsigned NOT NULL,
`dlr_status` tinyint(2) unsigned NOT NULL,
`dlr_substatus` tinyint(2) unsigned NOT NULL,
`dlr_final` tinyint(1) unsigned NOT NULL,
`dlr_refid` varchar(40) NOT NULL,
`dlr_phone` varchar(12) NOT NULL,
`dlr_charge` tinyint(3) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `message_id` (`message_id`),
KEY `dlr_status` (`dlr_status`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1468592 ;

Вот EXPLAIN SQL:

enter image description here

Ответы [ 3 ]

4 голосов
/ 25 сентября 2011

Есть хитрость.

Вместо этого, выбрав элемент MAX с подзапросом, вы соединяетесь с интересной таблицей дважды, как это показано:

SELECT
    m.id,
    m.user_id,
    m.api_key,
    m.to,
    m.message,
    m.sender_id,
    m.route,
    m.submission_reference,
    m.unique_submission_reference,
    m.reason_code,
    m.timestamp,
    d.id AS dlrid,
    d.dlr_status
FROM
    messages_sent m
JOIN
    delivery_receipts d
ON
    d.message_id = m.id
LEFT JOIN
    delivery_receipts d1
ON
    d1.message_id = m.id
    AND
    d1.id > d.id
WHERE
    d1.id IS NULL

Вторая временная таблица объединяется, для нее есть дополнительное условие, чтоПоле, которое вы хотите выбрать MAX, должно быть выше, чем в первой таблице.И отфильтруйте все строки, кроме тех, у которых нет другой строки, которая выше.

Таким образом, останется только максимальное количество строк.

Я изменил ваше ЛЕВОЕ СОЕДИНЕНИЕ на JOIN.Я не уверен, если вам нужно присоединиться там.Даже если вам это все равно будет работать.

Удивительно, но это намного быстрее, чем подзапрос.

Возможно, вы захотите попробовать другой вариант этой же идеи:

SELECT
    m.id,
    m.user_id,
    m.api_key,
    m.to,
    m.message,
    m.sender_id,
    m.route,
    m.submission_reference,
    m.unique_submission_reference,
    m.reason_code,
    m.timestamp,
    d.id AS dlrid,
    d.dlr_status
FROM
    messages_sent m
JOIN
(
SELECT d0.* FROM
    delivery_receipts d0
LEFT JOIN
    delivery_receipts d1
ON
    d1.message_id = d0.message_id
    AND
    d1.id > d0.id
WHERE
    d1.id IS NULL
) d
ON
    d.message_id = m.id

Убедитесь, что у вас есть многоколонный индекс для полей message_id и id в таблице delivery_receipts, может быть такой:

ALTER TABLE  `delivery_receipts` 
ADD INDEX  `idx` (  `message_id` ,  `id` );
0 голосов
/ 25 сентября 2011

Вы можете «кэшировать» часть вычислений в таблице delivery_receipts, просто добавьте логическое значение is_last_status в таблицу delivery_receipts.Используя простые триггеры, вы можете изменить значение при каждой вставке нового чека.

Чем запрос на выборку становится намного проще:

SELECT
  m.id,
  m.user_id,
  m.api_key,
  m.to,
  m.message,
  m.sender_id,
  m.route,
  m.submission_reference,
  m.unique_submission_reference,
  m.reason_code,
  m.timestamp,
  d.id AS dlrid,
  d.dlr_status
FROM
  messages_sent m
LEFT JOIN
  delivery_receipts d
ON
  d.message_id = m.id
WHERE
  d.is_last_status = true

Если mysql будет поддерживать частичные индексы, запрос может быть ускоренбольше.

0 голосов
/ 25 сентября 2011

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

Одной из проблем является сообщение о d.dlr_status.Попробуйте удалить это из списка сообщаемых столбцов и посмотрите, улучшится ли время запроса.

Вы получите максимально возможную производительность, если все будет храниться в messages_sent.Это больше не будет NF, но это вариант, если вам нужна производительность.Для этого создайте столбцы id и dlr_status в messages_sent и добавьте соответствующие триггеры INSERT, UPDATE и DELETE к delivery_receipts.Триггеры обновят соответствующие столбцы в messages_sent - это компромисс между временем запроса и временем обновления.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...