Самый быстрый способ сравнения наборов данных и обновления / вставки большого количества строк в большую таблицу MySQL? - PullRequest
2 голосов
/ 30 мая 2011

Схема

У меня есть база данных 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 для тестирования времени различных стратегий обновления и проверки их правильности в нескольких версиях. Это довольно долго, но я могу отредактировать вопрос, если люди думают, что это будет полезно.

Ответы [ 2 ]

1 голос
/ 30 мая 2011

Одно из ускорений для загрузки - НАГРУЗКА ДАННЫХ ИНФИЛЬ .

1 голос
/ 30 мая 2011

Поскольку у меня есть опыт ведения журнала аудита, вам лучше использовать две таблицы, например:

yourtable (id, col1, col2, version) -- pkey on id
yourtable_logs (id, col1, col2, version) -- pkey on (id, version)

Затем добавьте триггер обновления в вашу таблицу, который вставит предыдущую версию вyourtable_logs.

...