Как улучшить производительность InnoDB DELETE?

9

Итак, у меня есть эта таблица аудита (отслеживает действия над любой таблицей в моей базе данных):

CREATE TABLE `track_table` (
  `id` int(16) unsigned NOT NULL,
  `userID` smallint(16) unsigned NOT NULL,
  `tableName` varchar(255) NOT NULL DEFAULT '',
  `tupleID` int(16) unsigned NOT NULL,
  `date_insert` datetime NOT NULL,
  `action` char(12) NOT NULL DEFAULT '',
  `className` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `userID` (`userID`),
  KEY `tableID` (`tableName`,`tupleID`,`date_insert`),
  KEY `actionDate` (`action`,`date_insert`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1

и мне нужно начать архивирование устаревших предметов. Таблица выросла примерно до 50 миллионов строк, поэтому самым быстрым способом, которым я мог удалить строки, было удаление таблицы за раз (на основе tableName).

Это работает довольно хорошо, но на некоторых таблицах с интенсивной записью это не завершится. Мой запрос удаляет все элементы, с которыми связано deleteдействие в комбинации tupleID / tableName:

DELETE FROM track_table WHERE tableName='someTable' AND tupleID IN (
  SELECT DISTINCT tupleID FROM track_table
  WHERE tableName='someTable' AND action='DELETE' AND date_insert < DATE_SUB(CURDATE(), INTERVAL 30 day)
)

Я позволил этому запускаться на моем сервере в течение 3 дней, и он никогда не завершался для самой большой таблицы. Вывод объяснения (если я переключаю удаление, чтобы выбрать:

| id | select_type        | table       | type | possible_keys      | key     | key_len | ref        | rows    | Extra                        |
|  1 | PRIMARY            | track_table | ref  | tableID            | tableID | 257     | const      | 3941832 | Using where                  |
|  2 | DEPENDENT SUBQUERY | track_table | ref  | tableID,actionDate | tableID | 261     | const,func |       1 | Using where; Using temporary |

Поэтому 4 миллиона строк не должны занимать 3 дня, я бы подумал. Мой innodb_buffer_pool_size имеет значение 3 ГБ, а сервер не настроен на использование one_file_per_table. Какие еще способы можно улучшить производительность удаления InnoDB? (Запуск MySQL 5.1.43 на Mac OSX)

Дерек Дауни
источник

Ответы:

11

Вы можете удалить данные в пакетном режиме.

В SQL Server синтаксис - это delete top Xстроки из таблицы. Затем вы делаете это в цикле с транзакцией для каждого пакета (если, конечно, у вас более одного оператора), чтобы транзакции были короткими, а блокировки - только на короткие периоды.

В синтаксисе MySQL: DELETE FROM userTable LIMIT 1000

На это есть ограничения (например, нельзя использовать LIMITв удалениях с объединениями), но в этом случае вы можете сделать это таким образом.

Существует дополнительная опасность использования LIMITс DELETEкогда дело доходит до репликации; Удаленные строки иногда не удаляются в том же порядке на ведомом устройстве, как это было удалено на ведущем устройстве.

Мэриан
источник
6

Попробуйте использовать временную таблицу. Попробуйте что-то вроде этого:

Шаг 1) CREATE TABLE track_table_new LIKE track_table;

Шаг 2) INSERT INTO track_table_new SELECT * FROM track_table WHERE action='DELETE' AND date_insert >= DATE_SUB(CURDATE(), INTERVAL 30 day);

Шаг 3) ALTER TABLE track_table RENAME track_table_old;

Шаг 4) ALTER TABLE track_table_new RENAME track_table;

Шаг 5) DROP TABLE track_table_old;

Я не включил поле кортежа в Шаг 2. Пожалуйста, посмотрите, дает ли это желаемый эффект. Если это то, что вы хотите, вы можете полностью исключить поле кортежа, если только вы не используете поле кортежа по другим причинам.

RolandoMySQLDBA
источник
Это интересное решение. Мне нужно поле кортежа в таблице. tableName / tupleID - это неопределенный внешний ключ таблицы, которая регистрируется. Не определено, поскольку до недавнего времени эта таблица была MyISAM, которая не поддерживает внешние ключи.
Дерек Дауни
1

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

Поскольку MySQL не поддерживает полную функцию рыхлой сканировании индекса, вы можете попытаться настроить последовательность для KEY actionDate (action, date_insert)к KEY actionDate (date_insert, action). С префиксом date_insert MySQL должен использовать этот индекс для сканирования строк, которые предшествуют вашему состоянию datetime.

С таким индексом вы можете написать SQL как:

DELETE
FROM track_table
WHERE tableName='someTable'
    AND action='DELETE'
    AND date_insert < DATE_SUB(CURDATE(), INTERVAL 30 day)
LIMIT 1000 -- Your size of batch
Майк Лю
источник
1
| id | select_type        | table       | type | possible_keys      | key     | key_len | ref        | rows    | Extra                        |
|  1 | PRIMARY            | track_table | ref  | tableID            | tableID | 257     | const      | 3941832 | Using where                  |
|  2 | DEPENDENT SUBQUERY | track_table | ref  | tableID,actionDate | tableID | 261     | const,func |       1 | Using where; Using temporary |

Кулак, из вашего объяснения key_len такой большой => вам нужно уменьшить размер как можно меньше. Я думаю, что для вашего запроса лучше всего изменить поле данных типа действия с char (12) на tinyint, поэтому отображение данных выглядит следующим образом:

1: -> DELETE
2: -> UPDATE
3: -> INSERT
...

и вы можете изменить table_id вместо tablename тоже. DDL для лучшей производительности может:

CREATE TABLE `track_table` (
  `id` int(11) unsigned NOT NULL,
  `userID` smallint(6) unsigned NOT NULL,
  `tableid` smallint(6) UNSIGNED NOT NULL DEFAULT 0,
  `tupleID` int(11) unsigned NOT NULL,
  `date_insert` datetime NOT NULL,
  `actionid` tinyin(4) UNSIGNED NOT NULL DEFAULT 0,
  `className` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `userID` (`userID`),
  KEY `tableID` (`tableid`,`tupleID`,`date_insert`),
  KEY `actionDate` (`actionid`,`date_insert`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE `actions` (
  `id` tinyint(4) unsigned NOT NULL 
  `actionname` varchar(255) NOT NULL,
  PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE `table_name` (
  `id` tinyint(4) unsigned NOT NULL 
  `tablename` varchar(255) NOT NULL,
  PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

поэтому запрос может выглядеть так:

DELETE FROM track_table WHERE tableid=@tblid AND tupleID IN (
  SELECT DISTINCT tupleID FROM track_table
  WHERE tableid=@tblid AND actionid=@actionid AND date_insert < DATE_SUB(CURDATE(), INTERVAL 30 day)
).

Но самым быстрым способом было использование раздела. так что вы можете удалить раздел. В настоящее время моя таблица содержит более 40 миллионов строк. и обновлять ежечасно (400 000 строк обновляются каждый раз), и я могу удалить раздел curr_date и перезагрузить данные в таблицу. команда сброса очень быстро (<100 мс). Надеюсь, это поможет.

Тхан Нгуен
источник