【问题标题】:Efficient update statement in PostgreSQLPostgreSQL 中的高效更新语句
【发布时间】:2014-06-12 15:36:16
【问题描述】:

我在 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 中的这种利基行为很常见,并且对于大型数据仓库项目非常令人沮丧。

【问题讨论】:

  • 是的,它没有使用索引。 9M 行的行数是否准确?让我们看看您的表是如何定义的以及任何其他索引,只是为了完整。另外,对 SELECT count 做一个解释,并确保它正在使用索引。
  • 如果fresh 是一个布尔列,优化器可能不会考虑使用索引。通常,在布尔值上有一个索引是没有意义的。即使您考虑 NULL,您也将只有 3 个值:true、false 和 NULL,如果您假设均匀分布,这将导致 33% 的分区。这通常太多了,无法从索引中获得任何优势。 Postgres 是否支持查询提示或类似的东西?
  • 顺便说一句,您可以尝试一个针对小分区进行优化的部分索引,在您的情况下为“fresh=true”:create index mytable_fresh on MyTable(fresh) where fresh is true;
  • 扫描 950 万条记录还是使用索引都没有关系,无论哪种方式都不应该花一个小时。你说你没有触发器,但是你有外键吗?您的服务器在做什么:CPU、IO 等?
  • 除了@Martin 指出的由于cardinality 非常低而不使用索引之外,它还必须更新索引。

标签: sql postgresql postgresql-9.3 database-optimization


【解决方案1】:

这篇文章有点太长了,无法评论,所以我将其发布为答案。

在调查索引使用情况时,不要考虑截断记录,而要考虑查找匹配记录。说“嘿,你可以丢弃 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(根和第一级)的索引。查找 true 的 333 条记录每条记录需要 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 万条记录时有很多分页,更新不仅会导致对数据页的写访问,还会对索引页进行写访问,其中一些需要在索引树中进行上溢或下溢处理,从而导致更多的写入.使用部分索引时,这些瓶颈的影响会更低。不仅页面读取更少,而且更新的索引更小。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-01
    • 2015-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-26
    相关资源
    最近更新 更多