Brought to you by Rick James

The Problem

How to DELETE lots of rows from a large table? Here is an example of purging items older than 30 days:

   DELETE FROM tbl WHERE ts < CURRENT_DATE() - INTERVAL 30 DAY

If there are millions of rows in the table, this statement may take minutes, maybe hours. 几百万行数据。需要几分钟甚至几小时。

Any suggestions on how to speed this up?

Why it is a Problem

⚈  MyISAM will lock the table during the entire operation, thereby nothing else can be done with the table. 
    ⚈  InnoDB won't lock the table, but it will chew up a lot of resources, leading to sluggishness. 
    ⚈  InnoDB has to write the undo information to its transaction logs; this significantly increases the I/O required. 
    ⚈  Replication, being asynchronous, will effectively be delayed (on Slaves) while the DELETE is running.

⚈MyISAM将在整个操作过程中锁定表格,因此表格无法完成任何其他操作。 
    ⚈InnoDB不会锁定表格,但它会占用大量资源,导致迟缓。 
    ⚈InnoDB必须将撤消信息写入其事务日志; 这显着增加了所需的I / O. 
    ⚈在DELETE运行时,异步复制将在(Slaves)上有效延迟。

InnoDB and undo

To be ready for a crash, a transactional engine such as InnoDB will record what it is doing to a log file. To make that somewhat less costly, the log file is sequentially written. If the log files you have (there are usually 2)
fill up because the delete is really big, then the undo information spills into the actual data blocks, leading to even more I/O.

Deleting in chunks avoids some of this excess overhead.

Limited benchmarking of total delete elapsed time shows two observations:

⚈  Total delete time approximately doubles above some 'chunk' size (as opposed to below that threshold). I do not have a formula relating the log file size with the threshold cutoff. 
    ⚈  Chunk size below several hundred rows is slower. This is probably because the overhead of starting/ending each chunk dominates the timing.

Solutions

⚈  PARTITION -- Requires 5.1 and some careful setup, but is excellent for purging a time-base series. 
    ⚈  DELETE in chunks -- Carefully walk through the table N rows at a time.

分区 - 需要5.1和一些精心设置,但非常适合清除时基系列。 
    ⚈ 以chunk的形式   删除 - 一次小心地遍历表格N行。

PARTITION

The idea here is to have a sliding window of partitions. Let's say you need to purge news articles after 30 days. The "partition key" would be the datetime (or timestamp) that is to be used for purging, and the PARTITIONs would be BY RANGE. Every night, a cron job would come along and build a new partition for the next day, and drop the oldest partition.

Dropping a partition is essentially instantaneous, much faster than deleting that many rows. However, you must design the table so that the entire partition can be dropped. That is, you cannot have some items in a partition living longer than others.

PARTITION tables have a lot of restrictions, some are rather weird. You can either have no UNIQUE (or PRIMARY) key on the table, or every UNIQUE key must include the partition key. In this use case, the partition key is the datetime. It should not be the first part of the PRIMARY KEY (if you have a PRIMARY KEY).

You can PARTITION InnoDB tables. (Before Version 8.0, you could also partition MyISAM tables.)

Since two news articles could have the same timestamp, you cannot assume the partition key is sufficient for uniqueness of the PRIMARY KEY, so you need to find something else to help with that.

这里的想法是有一个分区的滑动窗口。假设您需要在30天后清除新闻文章。“分区键”将是用于清除的日期时间(或时间戳),PARTITIONs将是BY RANGE。每天晚上,一个cron作业会出现并为第二天构建一个新的分区,并删除最旧的分区。

删除分区基本上是即时的,比删除那么多行要快得多。但是,您必须设计表,以便可以删除整个分区。也就是说,您不能让分区中的某些项目比其他项目更长。

PARTITION表有很多限制,有些是相当奇怪的。您可以在表上没有UNIQUE(或PRIMARY)键,或者每个UNIQUE键都必须包含分区键。在此用例中,分区键是日期时间。它不应该是PRIMARY KEY的第一部分(如果你有一个PRIMARY KEY)。

你可以PARTITION InnoDB表。(在8.0之前,您还可以对MyISAM表进行分区。)

由于两篇新闻文章可能具有相同的时间戳,因此您不能假设分区键足以满足PRIMARY KEY的唯一性,因此您需要找到其他内容来帮助解决这个问题。

分区维护

PARTITIONing的参考实现需要MySQL 5.1。 关于PARTITION的MySQL文档

Reference implementation for Partition maintenance

PARTITIONing requires MySQL 5.1. MySQL docs on PARTITION

Deleting in Chunks

Although the discussion in this section talks about DELETE, it can be used for any other "chunking", such as, say, UPDATE, or SELECT plus some complex processing.

(This discussion applies to both MyISAM and InnoDB.)

When deleting in chunks, be sure to avoid doing a table scan. Also be sure to avoid OFFSET and LIMIT. The code below is good at that; it scans no more than 1001 rows in any one query. (The 1000 is tunable.) 
在以块的形式删除时,请务必避免进行表扫描。另外一定要避免OFFSETLIMIT。下面的代码很擅长; 它在任何一个查询中扫描不超过1001行。(1000是可调的。)

Assuming you have news articles that need to be purged, and you have a schema something like

   CREATE TABLE tbl
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
ts TIMESTAMP,
...
PRIMARY KEY(id)

Then, this pseudo-code is a good way to delete the rows older than 30 days: 
然后,这个伪代码是删除超过30天的行的好方法

   @a = 0
LOOP
DELETE FROM tbl
WHERE id BETWEEN @a AND @a+999
AND ts < DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
SET @a = @a + 1000
sleep 1 -- be a nice guy
UNTIL end of table

Notes (Most of these caveats will be covered later): 
    ⚈  It uses the PK instead of the secondary key. This gives much better locality of disk hits, especially for InnoDB. 
    ⚈  You could (should?) do something to avoid walking through recent days but doing nothing. Caution -- the code for this could be costly. 
    ⚈  The 1000 should be tweaked so that the DELETE usually takes under, say, one second. 
    ⚈  No INDEX on ts is needed. (This helps INSERTs a little.) 
    ⚈  If your PRIMARY KEY is compound, the code gets messier. (a fix is below) 
    ⚈  This code will not work without a numeric PRIMARY or UNIQUE key. (a fix is below) 
    ⚈  Read on, we'll develop messier code to deal with most of these caveats.

注释(大多数警告将在后面介绍): 
    ⚈它使用PK而不是辅助键。这提供了更好的磁盘命中位置,特别是对于InnoDB。 
    ⚈你可以(应该?)做些什么来避免走近最近几天但什么都不做。注意 - 此代码可能代价高昂。 
    ⚈应该调整1000,以便DELETE通常需要一秒钟。 
    ⚈不需要关于ts的索引。(这有助于INSERTs。) 
    ⚈如果您的PRIMARY KEY是复合的,代码会变得更加混乱。(修复如下) 
    ⚈如果没有数字PRIMARY或UNIQUE键,此代码将无法工作。(修正如下) 
    ⚈继续阅读,我们将开发更复杂的代码来处理大多数这些警告。

If there are big gaps in id values (and there will after the first purge), then

   @a = SELECT MIN(id) FROM tbl
LOOP
SELECT @z := id FROM tbl WHERE id >= @a ORDER BY id LIMIT 1000,1
If @z is null
exit LOOP -- last chunk
DELETE FROM tbl
WHERE id >= @a
AND id < @z
AND ts < DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
SET @a = @z
sleep 1 -- be a nice guy, especially in replication
ENDLOOP
# Last chunk:
DELETE FROM tbl
WHERE id >= @a
AND ts < DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)

That code works whether id is numeric or character, and it mostly works even if id is not UNIQUE. With a non-unique key, the risk is that you could be caught in a loop whenever @z==@a. That can be detected and fixed thus:

   ...
SELECT @z := id FROM tbl WHERE id >= @a ORDER BY id LIMIT 1000,1
If @z == @a
SELECT @z := id FROM tbl WHERE id > @a ORDER BY id LIMIT 1
...

The drawback is that there could be more than 1000 items with a single id. In most practical cases, that is unlikely.

If you do not have a primary (or unique) key defined on the table, and you have an INDEX on ts, then consider

   LOOP
DELETE FROM tbl
WHERE ts < DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
ORDER BY ts -- to use the index, and to make it deterministic
LIMIT 1000
UNTIL no rows deleted

This technique is NOT recommended because the LIMIT leads to a warning on replication about it being non-deterministic (discussed below).

InnoDB Chunking Recommendation

⚈  Have a 'reasonable' size for innodb_log_file_size. 
    ⚈  Use AUTOCOMMIT=1 for the session doing the deletions. 
    ⚈  Pick about 1000 rows for the chunk size. 
    ⚈  Adjust the row count down if asynchronous replication (Statement Based) causes too much delay on the Slaves or hogs the table too much.

⚈对innodb_log_file_size有一个“合理”的大小。 
    ⚈对执行删除的会话使用AUTOCOMMIT = 1。 
    ⚈为块大小选择大约1000行。 
    if如果异步复制(基于语句)导致Slaves上的延迟太多或者占用过多的表,则调整行数。

Iterating through a compound key

To perform the chunked deletes recommended above, you need a way to walk through the PRIMARY KEY. This can be difficult if the PK has more than one column in it.

To efficiently to do compound 'greater than':

要执行上面推荐的分块删除,您需要一种方法来遍历PRIMARY KEY。如果PK中有多个列,这可能很困难。

要有效地复合'大于':

Assume that you left off at ($g, $s) (and have handled that row):

   INDEX(Genus, species)
SELECT/DELETE ...
WHERE Genus >= '$g' AND ( species > '$s' OR Genus > '$g' )
ORDER BY Genus, species
LIMIT ...

Addenda: The above AND/OR works well in older versions of MySQL; this works better in newer versions:

      WHERE ( Genus = '$g' AND species  > '$s' ) OR Genus > '$g' )

A caution about using @variables for strings. If, instead of '$g', you use @g, you need to be careful to make sure that @g has the same CHARACTER SET and COLLATION as Genus, else there could be a charset/collation conversion on the fly that prevents the use of the INDEX. Using the INDEX is vital for performance. It may require a COLLATE clause on SET NAMES and/or the @g in the SELECT.

关于在字符串中使用@variables的注意事项。如果你使用@g代替'$ g',你需要小心确保@g具有相同的CHARACTER SET和COLLATION作为Genus,否则可能会有动态的charset / collat​​ion转换阻止使用INDEX。使用INDEX对性能至关重要。它可能需要SET NAMES上的COLLATE子句和/或SELECT中的@g。

Reclaiming the disk space

Note: Reclaiming disk space may not be necessary. After all, tomorrow's INSERTs will simply reuse the free space in the table. 
注意:可能不需要回收磁盘空间。毕竟,明天的INSERT将简单地重用表中的空闲空间。 
This is costly. (Switch to the PARTITION solution if practical.)

MyISAM leaves gaps in the table (.MYD file); OPTIMIZE TABLE will reclaim the freed space after a big delete. But it may take a long time and lock the table. 
MyISAM在表中留下空白(.MYD文件); OPTIMIZE TABLE将在大删除后回收释放的空间。但它可能需要很长时间才能锁定表格。

InnoDB is block-structured, organized in a BTree on the PRIMARY KEY. An isolated deleted row leaves a block less full. A lot of deleted rows can lead to coalescing of adjacent blocks. (Blocks are normally 16KB.)

In InnoDB, there is no practical way to reclaim the freed space from ibdata1, other than to reuse the freed blocks eventually.

If you have innodb_file_per_table = 0, the only option is to dump ALL tables, remove ibdata*, restart, and reload. That is rarely worth the effort and time.

InnoDB, even with innodb_file_per_table = 1, won't give space back to the OS, but at least it is only one table to rebuild with. In this case, something like this should work:

   CREATE TABLE new LIKE main;
INSERT INTO new SELECT * FROM main; -- This could take a long time
RENAME TABLE main TO old, new TO main; -- Atomic swap
DROP TABLE old; -- Space freed up here

You do need enough disk space for both copies. You must not write to the table during the process.

Deleting more than half a table

The following technique can be used for any combination of 
    ⚈  Deleting a large portion of the table more efficiently 
    ⚈  Add PARTITIONing 
    ⚈  Converting to innodb_file_per_table = ON 
    ⚈  Defragmenting

This can be done by chunking, or (if practical) all at once:

   -- Optional:  SET GLOBAL innodb_file_per_table = ON;
CREATE TABLE New LIKE Main;
-- Optional: ALTER TABLE New ADD PARTITION BY RANGE ...;
-- Do this INSERT..SELECT all at once, or with chunking:
INSERT INTO New
SELECT * FROM Main
WHERE ...; -- just the rows you want to keep
RENAME TABLE main TO Old, New TO Main;
DROP TABLE Old; -- Space freed up here

Notes: 
    ⚈  You do need enough disk space for both copies. 
    ⚈  You must not write to the table during the process. (Changes to Main may not be reflected in New.)

Non-deterministic Replication 非确定性复制

Any UPDATE, DELETE, etc with LIMIT that is replicated to slaves (via Statement Based Replication) _may_ cause inconsistencies between the Master and Slaves. This is because the actual order of the records discovered for updating/deleting may be different on the slave, thereby leading to a different subset being modified. To be safe, add ORDER BY to such statements. Moreover, be sure the ORDER BY is deterministic -- that is, the fields/expressions in the ORDER BY are unique.

任何带有LIMIT的UPDATE,DELETE等复制到从属(通过基于语句的复制)_may_导致主服务器和从服务器之间的不一致。这是因为在从属设备上发现的用于更新/删除的记录的实际顺序可能不同,从而导致修改不同的子集。为安全起见,请将ORDER BY添加到此类语句中。此外,确保ORDER BY是确定性的 - 也就是说,ORDER BY中的字段/表达式是唯一的。

An example of an ORDER BY that does not quite work: Assume there are multiple rows for each 'date':

   DELETE * FROM tbl ORDER BY date LIMIT 111

Given that id is the PRIMARY KEY (or UNIQUE), this will be safe:

   DELETE * FROM tbl ORDER BY date, id LIMIT 111

Unfortunately, even with the ORDER BY, MySQL has a deficiency that leads to a bogus warning in mysqld.err. See Spurious "Statement is not safe to log in statement format." warnings

Some of the above code avoids this spurious warning by doing

   SELECT @z := ... LIMIT 1000,1;  -- not replicated
DELETE ... BETWEEN @a AND @z; -- deterministic

That pair of statements guarantees no more than 1000 rows are touched, not the whole table.

Replication and KILL

If you KILL a DELETE (or any? query) on the Master in the middle of its execution, what will be Replicated?

If it is InnoDB, the query should be rolled back. (Exceptions??)

In MyISAM, rows are DELETEd as the statement is executed, and there is no provision for ROLLBACK. Some of the rows will be deleted, some won't. You probably have no clue of how much was deleted. In a single server, simply run the delete again. The delete is put into the binlog, but with error 1317. Since Replication is supposed to keep the Master and Slave in sync, and since it has no clue of how to do that, Replication stops and waits for manual intervention. In a HA (High Available) system using Replication, this is a minor disaster. Meanwhile, you need to go to each Slave(s) and verify that it is stuck for this reason, then do

   SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1;
START SLAVE;

Then (presumably) reexecuting the DELETE will finish the aborted task.

(That is yet another reason to move all your tables from MyISAM to InnoDB.)

SBR vs RBR; Galera

"Row Based Replication" implies that the rows to be deleted are written to the binlog. The bigger the rows, and the more rows that you delete in a single "chunk", the more replication will be impacted. The suggestion of "1000" rows per chunks may need to be adjusted. The tradeoff is between how soon all the chunks are finished versus how much impact each chunk has on other things going on in replication.

If the task is to "purge old data", then speed of completion is probably not important.

“基于行的复制”意味着要删除的行将写入binlog。行越大,并且在单个“块”中删除的行越多,将影响的复制越多。可能需要调整每块“1000”行的建议。权衡取决于所有块完成的时间与每个块对复制中其他事件的影响程度。

如果任务是“清除旧数据”,那么完成速度可能并不重要。

Postlog

The tips in this document apply to MySQL, MariaDB, and Percona.

Chunking via Common Schema
Similar - from OAK

Percona's package to do big deletes, etc: pt-archiver

Anecdote: 2 hours vs 5 days

Posted: 2010;   Refreshed: June, 2015;   Minor Refresh: Sep, 2017


-- Rick James

MySQL Documents by Rick James

HowTo Techniques for Optimizing Tough Tasks:

Partition Maintenance (DROP+REORG) for time series (includes list of PARTITION uses) 
Big DELETEs - how to optimize -- and other chunking advice, plus a use for PARTITIONing 
    Chunking lengthy DELETE/UPDATE/etc. 
Data Warehouse techniques: 
    Overview   Summary Tables   High speed ingestion   
Entity-Attribute-Value -- a common, poorly performing, design pattern (EAV); plus an alternative 
Find the nearest 10 pizza parlors -- efficient searching on Latitude + Longitude (another PARITION use) 
    Lat/Long representation choices 
Pagination, not with OFFSET, LIMIT 
Techniques on efficiently finding a random row (On beyond ORDER BY RAND()) 
GUID/UUID Performance (type 1 only) 
IP Range Table Performance -- or other disjoint ranges 
Rollup Unique User Counts 
Alter of a Huge table -- Mostly obviated by 5.6 
Latest 10 news articles -- how to optimize the schema and code for such 
Build and execute a "Pivot" SELECT (showing rows as columns) 
Find largest row for each group ("groupwise max")

Other Tips, Tuning, Debugging, Optimizations, etc...

Rick's RoTs (Rules of Thumb -- lots of tips) 
Memory Allocation (caching, etc) 
Character Set and Collation problem solver 
    Trouble with UTF-8   If you want case folding, but accent sensitivity, please file a request at http://bugs.mysql.com . 
    Python tips,   PHP tips,   other language tips 
    utf8 Collations   utf8mb4 Collations on 8.0 
Converting from MyISAM to InnoDB -- includes differences between them 
Compound INDEXes plus other insights into the mysteries of INDEXing 
Cookbook for Creating Indexes 
    Many-to-many mapping table   wp_postmeta   UNION+OFFSET 
MySQL Limits -- built-in hard limits 
    767-byte INDEX limit 
Galera, tips on converting to (Percona XtraDB Cluster, MariaDB 10, or manually installed) 
5.7's Query Rewrite -- perhaps 5.7's best perf gain, at least for this forum's users 
Request for tuning / slowlog info 
Best of MySQL Forum -- index of lots of tips, discussions, etc

Analyze MySQL Performance 
    Analyze VARIABLEs and GLOBAL STATUS     Analyze SlowLog

My slides from conferences 
Percona Live 4/2017 - Rick's RoTs (Rules of Thumb) - MySQL/MariaDB 
Percona Live 4/2017 - Index Cookbook - MySQL/MariaDB 
Percona Live 9/2015 - PARTITIONing - MySQL/MariaDB 
(older ones upon request)

学习笔记:MySQL Big DELETEs 删除大量数据的更多相关文章

  1. 吴裕雄--天生自然python学习笔记:pandas模块删除 DataFrame 数据

    Pandas 通过 drop 函数删除 DataFrarne 数据,语法为: 例如,删除陈聪明(行标题)的成绩: import pandas as pd datas = [[65,92,78,83,7 ...

  2. Oracle学习笔记(1)——查询及删除重复数据

      1.查找表中多余的重复记录(根据单个字段studentid)   select * from table_name where studentid in (select studentid fro ...

  3. MySQL学习笔记-MySQL体系结构总览

    MySQL体系结构总览 不管是用哪种数据库,了解数据库的体系结构都是极为重要的.MySQL体系结构主要由数据库和数据库实例构成. 数据库:物理操作系统文件或者其它文件的集合,在mysql中,数据库文件 ...

  4. VSTO学习笔记(十四)Excel数据透视表与PowerPivot

    原文:VSTO学习笔记(十四)Excel数据透视表与PowerPivot 近期公司内部在做一种通用查询报表,方便人力资源分析.统计数据.由于之前公司系统中有一个类似的查询使用Excel数据透视表完成的 ...

  5. Spring MVC 学习笔记11 —— 后端返回json格式数据

    Spring MVC 学习笔记11 -- 后端返回json格式数据 我们常常听说json数据,首先,什么是json数据,总结起来,有以下几点: 1. JSON的全称是"JavaScript ...

  6. 【Spring学习笔记-MVC-4】SpringMVC返回Json数据-方式2

    <Spring学习笔记-MVC>系列文章,讲解返回json数据的文章共有3篇,分别为: [Spring学习笔记-MVC-3]SpringMVC返回Json数据-方式1:http://www ...

  7. 【Spring学习笔记-MVC-3】SpringMVC返回Json数据-方式1

    <Spring学习笔记-MVC>系列文章,讲解返回json数据的文章共有3篇,分别为: [Spring学习笔记-MVC-3]SpringMVC返回Json数据-方式1:http://www ...

  8. Caffe学习笔记(三):Caffe数据是如何输入和输出的?

    Caffe学习笔记(三):Caffe数据是如何输入和输出的? Caffe中的数据流以Blobs进行传输,在<Caffe学习笔记(一):Caffe架构及其模型解析>中已经对Blobs进行了简 ...

  9. mysql 清空或删除表数据后,控制表自增列值的方法

    http://blog.sina.com.cn/s/blog_68431a3b0100y04v.html 方法1: truncate table 你的表名 //这样不但将数据全部删除,而且重新定位自增 ...

随机推荐

  1. Zend Studio下的PHP代码调试

    问题:Zend Studio无法调试php代码 安装Zend Debugger 下载 到http://downloads.zend.com/pdt/server-debugger下载最新的debugg ...

  2. python学习: 优秀Python学习资源收集汇总--转

    Python是一种面向对象.直译式计算机程序设计语言.它的语法简捷和清晰,尽量使用无异义的英语单词,与其它大多数程序设计语言使用大括号不一样,它使用縮进来定义语句块.与Scheme.Ruby.Perl ...

  3. js写的一个简单的手风琴菜单

    1 <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&q ...

  4. React 基础实例教程

    园子都荒废两个月了,实在是懒呀.. 近段时间用React开发了几个页面,在使用过程中着实碰到了一些问题,估计刚开始学习的伙伴们都会遇到各种各样的坑 总结记录一下,只看文档是碰不上问题的,内容基础也不基 ...

  5. Java设计模式学习记录-抽象工厂模式

    前言 上篇博客介绍了简单工厂模式和工厂方法模式,这次介绍抽象工厂模式,抽象工厂模式和工厂方法模式的区别在于需要创建对象的复杂程度上. 抽象工厂模式 抽象工厂模式是围绕着一个超级工厂创建其他工厂.这个超 ...

  6. Java设计模式学习记录-适配器模式

    前言 之前已经将五个创建型设计模式介绍完了,从这一篇开始介绍结构型设计模式,适配器模式就是结构型模式的一种,适配器要实现的效果是把“源”过渡到“目标”. 适配器模式 在开发过程中,使用一个已经存在的类 ...

  7. SpringMVC之使用Servlet原生API作为参数

    SpringMVC的handler接收如下的ServletAPI类型的参数: • HttpServletRequest • HttpServletResponse • HttpSession • ja ...

  8. Hibernate高效查询,只查询部分/指定字段

    公司使用 DetachedCriteria detachedCriteria = DetachedCriteria.forClass(PeBulletin.class); detachedCriter ...

  9. oracle中scott/tiger、sys、SYSDBA、system都是什么用

    scott 是个演示用户,是让你学习ORACLE用的 SYSDBA 不是用户,可以认为是个权限,超级权限详细点说吧            超级用户分两种 SYSDBA和SYSOPTSYSOPT 后面3 ...

  10. 局域网内客户端无法使用机器名连接SQLServer服务器

    在生产环境中有时会要求使用机器名连接SQLServer服务器,但有时捣好久都没法连上~ 针对这个问题做个简短记录,防止以后自己再遇到记不起原因,也方便一下其他同行! 废话不多说,作为工作多年的老家伙了 ...