是否可以构建一个单独的mysql查询(没有变量)来删除表中的所有记录,除了最新的N(按id desc排序)?
像这样的东西,只有它不起作用:)
delete from table order by id ASC limit ((select count(*) from table ) - N)
谢谢.
您不能以这种方式删除记录,主要问题是您不能使用子查询来指定LIMIT子句的值.
这工作(在MySQL 5.0.67中测试):
DELETE FROM `table` WHERE id NOT IN ( SELECT id FROM ( SELECT id FROM `table` ORDER BY id DESC LIMIT 42 -- keep this many records ) foo );
中间子查询是必需的.没有它我们会遇到两个错误:
SQL错误(1093):您无法在FROM子句中为更新指定目标表'table' - MySQL不允许您在直接子查询中引用要删除的表.
SQL错误(1235):此版本的MySQL尚不支持'LIMIT&IN/ALL/ANY/SOME子查询' - 您不能在NOT IN运算符的直接子查询中使用LIMIT子句.
幸运的是,使用中间子查询允许我们绕过这两个限制.
NickC指出,对于某些用例(例如此用例),可以显着优化此查询.我建议您阅读该答案,看看它是否适合您.
我知道我复活了一个很老的问题,但我最近遇到了这个问题,但需要能够很好地扩展到大量问题的东西.没有任何现有的性能数据,因为这个问题引起了很多关注,我想我会发布我发现的内容.
实际工作的解决方案是Alex Barrett的NOT IN
双子查询/方法(类似于Bill Karwin的)和Quassnoi的LEFT JOIN
方法.
遗憾的是,上述两种方法都会创建非常大的中间临时表,并且随着未删除的记录数量变大,性能会迅速降低.
我决定使用Alex Barrett的双子查询(谢谢!)但使用<=
而不是NOT IN
:
DELETE FROM `test_sandbox` WHERE id <= ( SELECT id FROM ( SELECT id FROM `test_sandbox` ORDER BY id DESC LIMIT 1 OFFSET 42 -- keep this many records ) foo )
它用于OFFSET
获取第N条记录的id 并删除该记录和所有先前的记录.
由于排序已经是这个问题的假设(ORDER BY id DESC
),<=
因此非常适合.
它更快,因为子查询生成的临时表只包含一个记录而不是N个记录.
我在两个测试用例中测试了上述三种工作方法和新方法.
两个测试用例使用10000个现有行,而第一个测试保留9000(删除最旧的1000),第二个测试保持50(删除最旧的9950).
+-----------+------------------------+----------------------+ | | 10000 TOTAL, KEEP 9000 | 10000 TOTAL, KEEP 50 | +-----------+------------------------+----------------------+ | NOT IN | 3.2542 seconds | 0.1629 seconds | | NOT IN v2 | 4.5863 seconds | 0.1650 seconds | | <=,OFFSET | 0.0204 seconds | 0.1076 seconds | +-----------+------------------------+----------------------+
有趣的是,该<=
方法在整个过程中看到了更好的性能,但实际上越多越好,而不是更糟.
不幸的是,通过其他人给,你不能在所有的答案DELETE
,并SELECT
从表中给出相同的查询.
DELETE FROM mytable WHERE id NOT IN (SELECT MAX(id) FROM mytable); ERROR 1093 (HY000): You can't specify target table 'mytable' for update in FROM clause
MySQL也不能支持LIMIT
子查询.这些是MySQL的局限性.
DELETE FROM mytable WHERE id NOT IN (SELECT id FROM mytable ORDER BY id DESC LIMIT 1); ERROR 1235 (42000): This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'
我能想出的最佳答案是分两个阶段完成:
SELECT id FROM mytable ORDER BY id DESC LIMIT n;
收集id并将它们组成逗号分隔的字符串:
DELETE FROM mytable WHERE id NOT IN ( ...comma-separated string... );
(通常将逗号分隔列表插入到SQL语句中会引入一些SQL注入的风险,但在这种情况下,值不是来自不受信任的源,它们是来自数据库本身的id值.)
注意:虽然这不能在单个查询中完成工作,但有时一个更简单,可以完成的解决方案是最有效的.
DELETE i1.* FROM items i1 LEFT JOIN ( SELECT id FROM items ii ORDER BY id DESC LIMIT 20 ) i2 ON i1.id = i2.id WHERE i2.id IS NULL
如果您的id是增量的,那么使用类似的东西
delete from table where id < (select max(id) from table)-N
要删除除最后N个记录以外的所有记录,您可以使用下面报告的查询。
这是一个查询,但是有很多语句,因此实际上并不是单个查询的原始查询方式。
另外,由于MySQL中的错误,您还需要一个变量和一个内置的(在查询中)准备好的语句。
希望它仍然有用...
NNN是行保持和theTable是你的工作表。
我假设您有一个名为id的自动递增记录
SELECT @ROWS_TO_DELETE := COUNT(*) - nnn FROM `theTable`; SELECT @ROWS_TO_DELETE := IF(@ROWS_TO_DELETE<0,0,@ROWS_TO_DELETE); PREPARE STMT FROM "DELETE FROM `theTable` ORDER BY `id` ASC LIMIT ?"; EXECUTE STMT USING @ROWS_TO_DELETE;
这种方法的好处是性能:我已经在具有大约13,000条记录的本地数据库上测试了查询,保留了最后1000条记录。运行时间为0.08秒。
来自已接受答案的脚本...
DELETE FROM `table` WHERE id NOT IN ( SELECT id FROM ( SELECT id FROM `table` ORDER BY id DESC LIMIT 42 -- keep this many records ) foo );
需要0.55秒。大约7倍。
测试环境:2011年末配备SSD的i7 MacBookPro上的mySQL 5.5.25