KEKSOV
@KEKSOV

Склейка двух таблиц по сложному условию

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

Итак, есть таблица, в которую записываются результаты некоторого эксперимента. События происходят нерегулярно, т.е. могут быть периоды, когда они происходят каждую секунду (но не чаще), а могут быть «застои» по несколько минут
CREATE TABLE experiment
(
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'Ключ записи',

    event_timestamp BIGINT UNSIGNED NOT NULL COMMENT 'Дата и время эксперимента',
    ex_value DECIMAL(11,4) NOT NULL COMMENT 'Результат эксперимента ',

    PRIMARY KEY ( id ),
    UNQUE ( event_timestamp )
) ENGINE=INNODB


Есть вторая таблица следующего вида, которая также содержит нерегулярные по времени события, в нее попадают случайные записи со значениями event_timestamp, которых может и не быть в таблице experiment:
CREATE TABLE random_events
(
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'Ключ записи',

    event_timestamp BIGINT UNSIGNED NOT NULL COMMENT 'Дата и время эксперимента',

    prev_event_timestamp BIGINT UNSIGNED NULL DEFAULT 0 COMMENT 'Дата и время предыдущего эксперимента',
    next_event_timestamp BIGINT UNSIGNED NULL DEFAULT 0  COMMENT 'Дата и время следующего эксперимента',

    PRIMARY KEY ( id ),
    INDEX ( event_timestamp )
) ENGINE=INNODB


Задача состоит в том, чтобы заполнить prev_event_timestamp и next_event_timestamp в таблице random_events соответствующими значениями из таблицы experiment. Иными словами, нужно узнать между какими двумя ближайшими экспериментами находится точка в таблице random_events.

Сейчас я делаю так для prev_event_timestamp:
UPDATE random_events
SET random_events.prev_event_timestamp = ( 
	SELECT event_timestamp 
	FROM experiment
	WHERE experiment.event_timestamp <= random_events.event_timestamp
	ORDER BY experiment.event_timestamp DESC
	LIMIT 1
)


и вот так для next_event_timestamp:
UPDATE random_events
SET random_events.next_event_timestamp = ( 
	SELECT event_timestamp 
	FROM experiment
	WHERE experiment.event_timestamp > random_events.event_timestamp
	ORDER BY experiment.event_timestamp ASC
	LIMIT 1
)


Все отлично работает, но с увеличением числа записей в random_events >10K все начинает очень тормозить. Причем, второй запрос отрабатывает в пять раз быстрее, чем первый.

Возможно, нужно подкрутить какие-то настройки в my.ini, создать индексы или изменить (переписать совсем иначе) запрос или что-то еще? Вдруг, кто-то уже эффективно решил аналогичную задачу у себя…

Спасибо.
  • Вопрос задан
  • 4509 просмотров
Решения вопроса 1
KEKSOV
@KEKSOV Автор вопроса
dummy2002, ivnik, mib

Спасибо, коллеги, ваши ответы подтолкнули мои мысли в правильном направлении. Если коротко, то идея состоит в создании временных таблиц, содержащих только нужный набор данных. До меня, вдруг, дошло, что мои тысячи случайных экспериментов можно очень красиво сгруппировать по времени события, т.к. они очень часто происходят в течении одной и той же минуты, это существенно снижает вычислительную нагрузку на БД. В итоге, удалось добиться снижения времени обработки с 5 минут, до 5 сек. :)

Вот работающий код, буду признателен за критику и советы:
-- После получения очередных суточных данных формируем временную таблицу, в которую попадают только те данные, которые попадают в диапазон необработанных случайных экспериментов
SELECT @minRandomTimestamp := ( SELECT event_timestamp FROM random_events ORDER BY event_timestamp ASC LIMIT 1 );
DROP TABLE IF EXISTS experiment_tmp;
CREATE TEMPORARY TABLE experiment_tmp AS 
	( -- Одна (старшая) запись из экспериментов за предыдущие сутки
		SELECT event_timestamp, ex_value
		FROM experiments
		WHERE event_timestamp < @minRandomTimestamp 
		ORDER BY event_timestamp DESC
		LIMIT 1 
	)
	UNION 
	( -- Все новые
		SELECT event_timestamp, ex_value
		FROM experiments 
		WHERE event_timestamp >= @minRandomTimestamp 
		ORDER BY event_timestamp ASC 
	) 
;
CREATE UNIQUE INDEX event_timestamp ON experiment_tmp (event_timestamp);

-- Определяем время последнего известного эксперимента
SELECT @maxExperimentTimestamp := ( SELECT event_timestamp FROM experiment_tmp ORDER BY event_timestamp DESC LIMIT 1 );

-- Выбираем во временную таблицу УНИКАЛЬНЫЕ (по времени) события, для которых появились суточные данные. Записей в этой таблице будет не больше, чем минут в сутках
DROP TABLE IF EXISTS random_events_tmp;
CREATE TEMPORARY TABLE random_events_tmp AS 
	SELECT event_timestamp, 0 prev_value_timestamp, 00000000000.0000 prev_value, 0 next_value_timestamp, 00000000000.0000 next_value
	FROM random_events 
	WHERE event_timestamp <= @maxExperimentTimestamp
	GROUP BY event_timestamp
;
CREATE UNIQUE INDEX pt ON random_events_tmp ( event_timestamp );

-- Вычисляем время предыдущего эксперимента
UPDATE random_events_tmp tr
SET tr.prev_value_timestamp = ( 
	SELECT event_timestamp 
	FROM experiment_tmp te
	WHERE te.event_timestamp <= tr.event_timestamp 
	ORDER BY te.event_timestamp DESC
	LIMIT 1
);

-- Проставляем величину предыдущего эксперимента
UPDATE random_events_tmp tr
LEFT JOIN experiment_tmp te ON te.event_timestamp = tr.prev_value_timestamp
SET tr.prev_value = te.ex_value;

-- Вычисляем время следующего эксперимента
UPDATE random_events_tmp tr
SET tr.next_value_timestamp = ( 
	SELECT event_timestamp 
	FROM experiment_tmp te
	WHERE te.event_timestamp > tr.event_timestamp
	ORDER BY te.event_timestamp ASC
	LIMIT 1
);

-- Проставляем величину следующего эксперимента
UPDATE random_events_tmp tr
LEFT JOIN experiment_tmp te ON te.event_timestamp = tr.next_value_timestamp
SET tr.next_value = te.ex_value;

-- Переносим данные в таблицу случайных экспериментов. Это самый долгий запрос > 3 сек.
UPDATE random_events f
LEFT JOIN random_events_tmp tr ON f.event_timestamp = tr.event_timestamp
SET 
	  f.prev_value_timestamp = tr.prev_value_timestamp
	, f.next_value_timestamp = tr.next_value_timestamp
	, f.prev_value = tr.prev_value
	, f.next_value = tr.next_value
;

-- После этого происходит обработка данных на клиенте и обработанные записи удаляются из таблицы random_events
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 3
@mib
Сперва общие рекомендации (на правах имхо :)

Для начала — innodb — довольно медленный движок с транзакциями и откатами. Если эти свойства таблиц вам не нужны — можно попробовать переделать на myisam. Тогда запросы на апдейты будут выполняться гораздо быстрее.

Можно пойти дальше и создать временную таблицу на движке «memory» с результатом слияния двух таблиц, то есть выполнять апдейты в памяти.
Ответ написан
ivnik
@ivnik
Второй запрос работает быстро т.к. ограничение целостности UNIQUE по полю event_timestamp создало индекс. А оптимизатор похоже догадался что LIMIT+ORDER_BY не требует сортировки.
В таком случае самое простое, что можно сделать, это хранить в таблице experiment ссылку на предыдущий эксперимент (в виде id или сразу в виде timestamp), и вытаскивать эту запись одним более быстрым запросом.
Ответ написан
KEKSOV
@KEKSOV Автор вопроса
UPDATE random_events
SET random_events.next_event_timestamp = ( 
    SELECT MIN( event_timestamp )
    FROM experiment
    WHERE experiment.event_timestamp > random_events.event_timestamp
)


с MAX аналогично.

P.S. А если сделать два поля в таблицe experiment


Весьма проблематично. В процедуру импорта данных без стакана не влезешь, да и значения эти нужны только один раз в жизни каждого измерения, а записей сотни миллионов, это было бы слишком избыточно. На самом деле, я уже все придумал :) Сейчас дотестирую и выложу свое «решение» на поругание.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы