如何在PostgreSQL中通过排序删除固定数量的行?


107

我正在尝试将一些旧的MySQL查询移植到PostgreSQL,但是我遇到了麻烦:

DELETE FROM logtable ORDER BY timestamp LIMIT 10;

PostgreSQL不允许在其删除语法中进行排序或限制,并且该表没有主键,因此我不能使用子查询。另外,我想保留查询完全删除给定数字或记录的行为-例如,如果表包含30行,但它们都具有相同的时间戳,我仍然要删除10行,尽管没关系其中10。

所以; 如何在PostgreSQL中通过排序删除固定数量的行?

编辑:没有主键意味着没有log_id列或类似。啊,遗留系统的乐趣!


1
为什么不添加主键?在postgresql中占一席之地:alter table foo add column id serial primary key
韦恩·康拉德

那是我最初的方法,但是其他要求阻止了它。
2011年

Answers:


159

您可以尝试使用ctid

DELETE FROM logtable
WHERE ctid IN (
    SELECT ctid
    FROM logtable
    ORDER BY timestamp
    LIMIT 10
)

ctid是:

行版本在其表中的物理位置。请注意,尽管ctid可以使用来快速定位行版本,但是ctid如果通过更新或移动,行的内容将会改变VACUUM FULL。因此,ctid它不能用作长期行标识符。

还有,oid但只有在创建表时特别要求时才存在。


这可行,但是它的可靠性如何?我需要注意任何“陷阱”吗?VACUUM FULL如果ctid查询运行时它们更改了表中的值,则是否有可能或autovacuum引起问题?
2011年

2
我认为增量VACUUM不会更改ctid。因为那只是压缩在每个页面中,所以ctid只是行号而不是页面偏移量。VACUUM FULL或CLUSTER操作更改ctid,但这些操作首先在表上获得访问排他锁。
araqnid

@Whatsit:我对ctid文档的印象ctid是足够稳定,可以使此DELETE正常工作,但不够稳定,例如,可以将另一个表作为贫民窟-FK放进去。大概您不需要UPDATE,logtable所以您不必担心ctids的变化并VACUUM FULL会锁定表(postgresql.org/docs/current/static/routine-vacuuming.html),因此您不必担心ctids可以改变的另一种方式。@araqnid的PostgreSQL-Fu非常强大,文档同意他启动。
亩太短了,

感谢你们两个人的澄清。我确实调查过文档,但不确定我是否正确解释了它们。在此之前,我从未遇到过ctid。
Whatsit 2011年

这实际上是一个非常糟糕的解决方案,因为Postgres无法在联接中使用TID扫描(IN是一种特殊情况)。如果您看一下计划,那将是非常糟糕的。因此,“非常快速”仅在您明确指定CTID时适用。所说的是从版本10开始的
。– greatvovan

53

Postgres文档建议使用数组而不是IN和子查询。这应该工作更快

DELETE FROM logtable 
WHERE id = any (array(SELECT id FROM logtable ORDER BY timestamp LIMIT 10));

这个和其他一些技巧可以在这里找到


@Konrad Garus在这里,您可以链接 “快速删除前n行”
评论家,2011年

1
@BlakeRegalia否,因为指定的表中没有主键。这将删除在前10个中找到的带有“ ID”的所有行。如果所有行具有相同的ID,则将删除所有行。
菲利普·怀特豪斯

6
如果any (array( ... ));比更快,in ( ... )这听起来像是查询优化器中的错误-它应该能够发现该转换并对数据本身执行相同的操作。
rjmunro 2015年

1
我发现此方法比IN在上使用慢得多UPDATE(可能有所不同)。
jmervine '16

1
在12 GB表上进行测量:第一个查询为450..1000 ms,第二个为5..7秒:快速的:从cs_logging中删除,其中id =任何(数组(从cs_logging中选择id,其中date_created <now()-间隔'1天'* 30和partition_key如'%I'的id限制为ID限制500))慢速一:从cs_logging中删除id所在的位置(从cs_logging中选择id,date_created <now()-间隔'1天'* 30和partition_key如'%我的ID限制为500)。使用ctid的速度要慢很多(几分钟)。
Guido Leenders


2

假设您要删除任何10条记录(无顺序),则可以执行以下操作:

DELETE FROM logtable as t1 WHERE t1.ctid < (select t2.ctid from logtable as t2  where (Select count(*) from logtable t3  where t3.ctid < t2.ctid ) = 10 LIMIT 1);

对于我的用例,删除1000万条记录,事实证明这更快。


1

您可以编写一个过程来循环删除各行,该过程可以使用一个参数来指定要删除的项目数。但是,与MySQL相比,这有点过分了。


0

如果您没有主键,则可以将数组Where IN语法与复合键一起使用。

delete from table1 where (schema,id,lac,cid) in (select schema,id,lac,cid from table1 where lac = 0 limit 1000);

这对我有用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.