0

我在 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值添加了一个部分索引,这大大加快了它的速度(22 毫秒)。不确定为什么部分索引有效而通用索引被完全忽略。我发现 Postgres 中的这种利基行为很常见,并且对于大型数据仓库项目非常令人沮丧。

4

1 回答 1

0

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

在调查索引使用时,不要考虑截断记录,而要考虑查找匹配记录。说“嘿,你可以丢弃 66% 的记录,剩下的一组将是小菜一碟”看起来很诱人。但是 DBMS 要做的是找到与您的搜索关键字匹配的记录。如果没有提示在哪里可以找到某条记录,DBMS 必须扫描表并将每条记录与搜索键进行比较。假设每条记录都适合一页。这意味着当表在表扫描中有记录时,DBMS 必须读取n页面。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 文档中,它说阈值是“所有表行的百分之几”

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

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

于 2014-06-13T15:05:40.403 回答