Bagaimana cara meningkatkan kinerja InnoDB DELETE?

9

Jadi saya memiliki tabel audit ini (melacak tindakan pada tabel apa pun di database saya):

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

dan saya harus mulai mengarsipkan item yang sudah ketinggalan zaman. Tabel telah berkembang menjadi sekitar 50 juta baris, jadi cara tercepat saya bisa menghapus baris adalah menghapusnya dalam satu waktu (berdasarkan pada tableName).

Ini berfungsi cukup baik tetapi pada beberapa tabel yang berat, itu tidak akan lengkap. Kueri saya menghapus semua item yang memiliki deletetindakan terkait pada kombinasi 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)
)

Saya membiarkan ini berjalan di server saya selama 3 hari dan tidak pernah selesai untuk tabel terbesar. Hasil penjelasan (jika saya mengganti hapus untuk memilih:

| 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 |

Jadi 4 juta baris seharusnya tidak perlu 3 hari untuk dihapus, saya pikir. Saya mengatur Innodb_buffer_pool_size saya menjadi 3GB, dan server tidak diatur untuk menggunakan one_file_per_table. Apa cara lain yang bisa saya lakukan untuk meningkatkan kinerja penghapusan InnoDB? (Menjalankan MySQL 5.1.43 di Mac OSX)

Derek Downey
sumber

Jawaban:

11

Anda dapat menghapus data dalam batch.

Dalam SQL Server, sintaksnya adalah delete top Xbaris dari tabel. Anda kemudian melakukannya dalam satu lingkaran, dengan transaksi untuk setiap batch (jika Anda memiliki lebih dari satu pernyataan, tentu saja), jadi untuk menjaga transaksi tetap pendek dan menjaga kunci hanya untuk periode pendek.

Dalam sintaks MySQL: DELETE FROM userTable LIMIT 1000

Ada batasan untuk itu (misalnya, tidak dapat digunakan LIMITdalam penghapusan dengan gabungan) tetapi dalam hal ini Anda mungkin dapat melakukannya dengan cara itu.

Ada bahaya tambahan untuk digunakan LIMITdengan DELETEketika datang ke replikasi; baris yang dihapus terkadang tidak dihapus dalam urutan yang sama pada slave seperti yang telah dihapus pada master.

Marian
sumber
6

Coba gunakan pendekatan tabel temp. Coba sesuatu seperti ini:

Langkah 1) CREATE TABLE track_table_new LIKE track_table;

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

Langkah 3) ALTER TABLE track_table RENAME track_table_old;

Langkah 4) ALTER TABLE track_table_new RENAME track_table;

Langkah 5) DROP TABLE track_table_old;

Saya tidak memasukkan bidang tuple pada Langkah 2. Silakan lihat apakah ini menghasilkan efek yang diinginkan. Jika ini yang Anda inginkan, Anda mungkin ingin membuang seluruhnya bidang tuple kecuali Anda menggunakan bidang tuple karena alasan lain.

RolandoMySQLDBA
sumber
Itu solusi yang menarik. Saya perlu bidang tuple di tabel. tableName / tupleID adalah kunci asing dari tabel yang sedang didata. Tidak ditentukan karena hingga saat ini, tabel ini adalah MyISAM, yang tidak mendukung kunci asing.
Derek Downey
1

Penghapusan baris yang tidak diinginkan dalam batch harus membuat operasi lain bisa berjalan. Tetapi penghapusan operasi Anda memiliki kondisi, jadi pastikan bahwa ada indeks yang sesuai pada kolom di atas kondisi.

Karena MySQL tidak mendukung fungsi lengkap longgar indeks scan, Anda dapat mencoba untuk menyesuaikan urutan untuk KEY actionDate (action, date_insert)untuk KEY actionDate (date_insert, action). Dengan awalan 'date_insert', MySQL harus menggunakan indeks ini untuk memindai baris yang sebelum kondisi datetime Anda.

Dengan indeks tersebut, Anda dapat menulis SQL sebagai:

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
Mike Lue
sumber
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, dari Anda menjelaskan key_len begitu besar => Anda perlu menurunkan ukuran sekecil mungkin. Untuk kueri Anda, saya pikir cara terbaik adalah mengubah tipe data bidang tindakan dari char (12) menjadi tinyint, sehingga pemetaan data terlihat seperti:

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

dan Anda dapat mengubah table_id sebagai ganti nama tab juga. DDL untuk kinerja terbaik dapat:

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;

sehingga kueri dapat berjalan terlihat seperti:

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)
).

Tetapi cara tercepat adalah menggunakan partisi. sehingga Anda dapat menjatuhkan partisi. Saat ini, meja saya sudah mendapat lebih dari 40mil baris. dan perbarui setiap jam (pembaruan 400k baris untuk setiap kali), dan saya dapat menjatuhkan partisi curr_date dan memuat ulang data ke dalam tabel. perintah drop sangat cepat (<100ms). Semoga bantuan ini.

Thanh Nguyen
sumber