PostgreSQL中的高效更新语句

时间:2014-06-12 15:36:16

标签: sql postgresql postgresql-9.3 database-optimization

我在PostgreSQL 9.3数据库中有一个大表(大约10M条记录),我试图运行一个简单的更新语句:

UPDATE mytable SET fresh = null WHERE fresh = true;

它已经运行了一个多小时而没有任何结束。

但是,我知道:

SELECT count(*) FROM mytable WHERE fresh = true;

在几秒钟内运行,它只会影响7000条记录。

为什么我的更新需要这么长时间?我的数据库中没有任何触发器,fresh列的编号为:

CREATE INDEX mytable_fresh ON mytable USING btree (fresh);

正在运行EXPLAIN UPDATE mytable SET fresh = null WHERE fresh = true

Update on mytable  (cost=0.00..455553.18 rows=9525759 width=167)
  ->  Seq Scan on mytable  (cost=0.00..455553.18 rows=9525759 width=167)
        Filter: fresh

我是否更正,它正在扫描所有950万条记录而不使用索引?如果是这样,我该如何解决这个问题?

编辑:我的fresh列是可以为空的布尔类型。我为true值添加了一个部分索引,并大大加快了它(22ms)。在完全忽略泛型索引时,不确定为什么部分索引有效。我发现Postgres中的这种利基行为很常见,并且对大型数据仓库项目非常沮丧。

1 个答案:

答案 0 :(得分:0)

这篇文章对于评论来说有点太长了,所以我把它作为答案发布。

在调查索引使用时,不要考虑截断记录,请考虑查找匹配记录。可能看起来很有诱惑力地说“嘿,你可以丢弃66%的记录,剩下的一组将是小菜一碟”。但DBMS要做的是找到与您的搜索关键字匹配的记录。如果没有提示在何处查找某条记录,则DBMS必须扫描该表并将每条记录与搜索键进行比较。假设每条记录都适合一页。这意味着当表格在表扫描中有n个记录时,DBMS必须读取n个页面。

如果DBMS可以减少查找与搜索关键字匹配的所有记录所需的读取次数,则查询的性能会提高。这是通过索引来完成的,该索引就像一张内容表。 DBMS可以在索引中查找查找某些记录的位置。显然,使用索引会创建额外的页面读取,因为DBMS也必须读取索引页面。只有在(number of index page reads + number of data page reads)<(number of page reads in table scan)时才能使用索引。

想象一个包含1000条记录的表,让一条布尔列和333条记录true,其余为false。让我们进一步假设你有一个depth=2的索引(根和第一级)。查找333 true条记录需要每条记录3页读取:2个索引页读取和1个数据页读取。如您所见,333 * 3 = 999对表扫描没有影响(1000个数据页读取)。如果值均匀分布,则使用索引将导致500 * 3 = 1500页读取,而通过表扫描读取1000次。在部分索引的Postgres文档中,它表示阈值为"a few percent of all the table rows"

对于布尔列中的低基数集,优化程序(对于Postgres它的查询计划程序AFAIK)可能会默认将索引的使用视为错误选择。使用部分索引可以覆盖此选择。这里DBMS将对部分索引进行索引扫描,因此读取的估计值为7000*(index tree depth+data pages per record)

考虑到原始方案中执行时间超过一小时的情况,您可能还会遇到一些内存或I / O问题。扫描950万条记录时可能会有大量的分页,而且更新不仅会导致对数据页的写访问,还会对索引页进行写访问,其中一些将需要索引树中的溢出或下溢处理,从而导致更多写入。使用部分索引时,这些瓶颈的影响会更低。不仅页面读取次数较少,更新的索引也较小。