Схема
У меня есть база данных MySQL с одной большой таблицей (скажем, 5 миллионов строк). В этой таблице есть несколько полей для фактических данных, необязательное поле для комментариев и поля для записи, когда строка была добавлена впервые и когда данные удалены. Чтобы упростить один столбец «данных», он выглядит примерно так:
+----+------+---------+---------+----------+
| id | data | comment | created | deleted |
+----+------+---------+---------+----------+
| 1 | val1 | NULL | 1 | 2 |
| 2 | val2 | nice | 1 | NULL |
| 3 | val3 | NULL | 2 | NULL |
| 4 | val4 | NULL | 2 | 3 |
| 5 | val5 | NULL | 3 | NULL |
Эта схема позволяет нам просматривать любую предыдущую версию данных благодаря полям created
и deleted
, например,
SET @version=1;
SELECT data, comment FROM MyTable
WHERE created <= @version AND
(deleted IS NULL OR deleted > @version);
+------+---------+
| data | comment |
+------+---------+
| val1 | NULL |
| val2 | nice |
Текущая версия данных может быть получена более просто:
SELECT data, comment FROM MyTable WHERE deleted IS NULL;
+------+---------+
| data | comment |
+------+---------+
| val2 | nice |
| val3 | NULL |
| val5 | NULL |
DDL:
CREATE TABLE `MyTable` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` varchar(32) NOT NULL,
`comment` varchar(32) DEFAULT NULL,
`created` int(11) NOT NULL,
`deleted` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `data` (`data`,`comment`)
) ENGINE=InnoDB;
Обновление
Периодически поступает новый набор данных и комментариев. Это может быть довольно большим, говорят полмиллиона строк. Мне нужно обновить MyTable
, чтобы этот новый набор данных хранился в нем. Это значит:
- «Удаление» старых строк. Обратите внимание на «пугающие кавычки» - мы фактически не удаляем строки из
MyTable
. Мы должны установить в поле deleted
новую версию N
. Это должно быть сделано для всех строк в MyTable
, которые находятся в предыдущей версии N-1
, но не находятся в новом наборе.
- Вставка новых строк. Все строки, которые находятся в новом наборе и не имеют версии
N-1
в MyTable
, должны быть добавлены как новые строки с полем created
, установленным для новой версии N
и deleted
как NULL.
Некоторые строки в новом наборе могут совпадать с существующими строками в MyTable
в версии N-1
, и в этом случае делать нечего.
Мое текущее решение
Учитывая, что нам нужно «разграничить» два набора данных, чтобы обработать удаления, мы не можем просто прочитать новые данные и выполнить вставки соответствующим образом. Я не могу придумать, как выполнить операцию сравнения без предварительной записи всех новых данных во временную таблицу. Итак, моя стратегия выглядит так:
-- temp table uses MyISAM for speed.
CREATE TEMPORARY TABLE tempUpdate (
`data` char(32) NOT NULL,
`comment` char(32) DEFAULT NULL,
PRIMARY KEY (`data`),
KEY (`data`, `comment`)
) ENGINE=MyISAM;
-- Bulk insert thousands of rows
INSERT INTO tempUpdate VALUES
('some new', NULL),
('other', 'comment'),
...
-- Start transaction for the update
BEGIN;
SET @newVersion = 5; -- Worked out out-of-band
-- Do the "deletions". The join selects all non-deleted rows in MyTable for
-- which the matching row in tempUpdate does not exist (tempUpdate.data is NULL)
UPDATE MyTable
LEFT JOIN tempUpdate
ON MyTable.data = tempUpdate.data AND
MyTable.comment <=> tempUpdate.comment
SET MyTable.deleted = @newVersion
WHERE tempUpdate.data IS NULL AND
MyTable.deleted IS NULL;
-- Delete all rows from the tempUpdate table that match rows in the current
-- version (deleted is null) to leave just new rows.
DELETE tempUpdate.*
FROM MyTable RIGHT JOIN tempUpdate
ON MyTable.data = tempUpdate.data AND
MyTable.comment <=> tempUpdate.comment
WHERE MyTable.id IS NOT NULL AND
MyTable.deleted IS NULL;
-- All rows left in tempUpdate are new so add them.
INSERT INTO MyTable (data, comment, created)
SELECT DISTINCT tempUpdate.data, tempUpdate.comment, @newVersion
FROM tempUpdate;
COMMIT;
DROP TEMPORARY TABLE IF EXISTS tempUpdate;
Вопрос (наконец-то)
Мне нужно найти самый быстрый способ выполнить эту операцию обновления. Я не могу изменить схему для MyTable
, поэтому любое решение должно работать с этим ограничением. Можете ли вы придумать более быстрый способ выполнения операции обновления или предложить ускорить мой существующий метод?
У меня есть скрипт Python для тестирования времени различных стратегий обновления и проверки их правильности в нескольких версиях. Это довольно долго, но я могу отредактировать вопрос, если люди думают, что это будет полезно.