Описание проблемы
У меня есть таблица аудита, которая содержит историю изменений некоторых объектов. Аудит содержит уникальный идентификатор события аудита, идентификатор изменяемого объекта, дату изменения, измененное свойство, а также значения до и после изменения и другие столбцы.
Что мне нужно сделать, это запросить данные аудита и получить дату, когда это же поле было ранее изменено для того же объекта. Поэтому мне нужно посмотреть аудит еще раз и для каждой записи аудита добавить предыдущую аналогичную запись с ее датой в качестве предыдущей даты изменения.
Схема и данные
Схема таблицы имеет идентификатор (id) в качестве первичного ключа и идентификатор объекта (parent_id) в качестве индекса. Больше ничего не индексируется. В моем тестовом примере у меня есть примерно 150 объектов с около 80 тыс. Записей аудита для них.
Решение
Есть два очевидных решения подзапросов и левого соединения.
В левом соединении я в основном снова соединяю ту же самую таблицу аудита с самим оператором соединения, проверяя, соответствуют ли изменения объекта, поля и значения, изменения старше текущего изменения и выбирают максимальную дату изменения и, наконец, только забрать одно последнее предыдущее изменение, которое я сгруппировал по идентификатору. Если предыдущее изменение не найдено, используйте дату создания самого объекта.
LEFT JOIN SQL
SELECT `audit`.`id` AS `id`,
`audit`.`parent_id` AS `parent_id`,
`audit`.`date_created` AS `date_created`,
COALESCE(MAX(`audit_prev`.`date_created`), `audit_parent`.`date_entered`) AS `date_created_before`,
`audit`.`field_name` AS `field_name`,
`audit`.`before_value_string` AS `before_value_string`,
`audit`.`after_value_string` AS `after_value_string`
FROM `opportunities_audit` `audit`
LEFT JOIN `opportunities_audit` `audit_prev`
ON(`audit`.`parent_id` = `audit_prev`.`parent_id`
AND `audit_prev`.`date_created` < `audit`.`date_created`
AND `audit_prev`.`after_value_string` = `audit`.`before_value_string`
AND `audit`.`field_name` = `audit_prev`.`field_name`)
LEFT JOIN `opportunities` `audit_parent` ON(`audit`.`parent_id` = `audit_parent`.`id`)
GROUP BY `audit`.`id`;
Логика подзапроса довольно похожа, но вместо группировки и использования функции MAX у меня просто есть порядок по дате DESC и LIMIT 1
SELECT `audit`.`id` AS `id`,
`audit`.`parent_id` AS `parent_id`,
`audit`.`date_created` AS `date_created`,
COALESCE((SELECT `audit_prev`.`date_created`
FROM `opportunities_audit` AS `audit_prev`
WHERE
(`audit_prev`.`parent_id` = `audit`.`parent_id`)
AND (`audit_prev`.`date_created` < `audit`.`date_created`)
AND (`audit_prev`.`after_value_string` = `audit`.`before_value_string`)
AND (`audit_prev`.`field_name` = `audit`.`field_name` )
ORDER BY `date_created` DESC
LIMIT 1
), `audit_parent`.`date_entered`) AS `date_created_before`,
`audit`.`field_name` AS `field_name`,
`audit`.`before_value_string` AS `before_value_string`,
`audit`.`after_value_string` AS `after_value_string`
FROM `opportunities_audit` `audit`
LEFT JOIN `opportunities` `audit_parent` ON(`audit`.`parent_id` = `audit_parent`.`id`);
Оба запроса дают идентичные наборы результатов.
Выпуск
Когда я запускаю запрос в phpMyAdmin, решение с объединением занимает примерно 2 мсек, чтобы вернуть результат. Тем не менее, phpMyAdmin говорит, что запрос занял 0,04 секунды. Когда я запускаю решение подзапроса, результат возвращается немедленно, и сообщаемое время выполнения phpMyAdmin составляет примерно 0,06 секунды.
Так что мне трудно понять, откуда эта разница в реальном времени исполнения. Первоначально я предполагал, что проблема будет связана с автоматическими LIMITS phpMyAdmin для возвращаемого набора данных - в то время как результат содержит 80 тыс. Строк, он отображает только 25. Но добавление LIMIT вручную в запросы заставляет их оба выполняться быстро.
Кроме того, при выполнении запросов из командной строки инструмент mysql возвращает полные наборы результатов для обоих запросов, и сообщенное время выполнения соответствует фактическому времени выполнения, а метод, использующий объединения, все еще примерно в 1,5 раза быстрее, чем подзапрос.
Из данных профилировщика кажется, что большая часть этого времени ожидания тратится на отправку данных. Как это занимает отправка данных занимает порядка минут, а все остальное в порядке микросекунд.
И все же, почему поведение phpMyAdmin так сильно отличается в случае двух запросов?