Як покращити продуктивність 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. Будь ласка, подивіться, чи це дає бажаний ефект. Якщо це те, що ви хочете, ви можете взагалі викопати кортежне поле, якщо ви не використовуєте поле кортежу з інших причин.


Це цікаве рішення. Мені потрібно поле кортежу в таблиці. tableName / tupleID - невизначений зовнішній ключ таблиці, що реєструється. Не визначено, оскільки донедавна ця таблиця була MyISAM, яка не підтримує сторонні ключі.
Дерек Дауні

1

Видалення небажаних рядків у партії повинно забезпечити працездатність інших операцій. Але ваше видалення операції має умови, тому переконайтеся, що є відповідний індекс для стовпців за умовами.

Оскільки MySQL не підтримує повну функцію пухкої скануванні індексу, ви можете спробувати налаштувати послідовність для KEY actionDate (action, date_insert)до KEY actionDate (date_insert, action). З префіксом 'date_insert', MySQL повинен використовувати цей індекс для сканування рядків, що передували вашому стану дати.

З таким індексом ви можете написати 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 |

-Fist, з вашого пояснення key_len так великий => вам потрібно зменшити розмір якомога менше. Для вашого запиту я вважаю, що найкращим способом є зміна типу даних поля дії з char (12) на tinyint, щоб відображення даних виглядало так:

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

і ви також можете змінити table_id замість назви таблиці. 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 мільйонів рядів. і оновлювати щогодини (оновлення 400k рядків щоразу), і я можу скинути розділ curr_date та перезавантажити дані в таблицю. команда drop дуже швидко (<100 мс). Сподіваюся, що це допоможе.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.