【问题标题】:Postgres count(*) optimization ideaPostgres count(*) 优化思路
【发布时间】:2016-01-10 13:23:41
【问题描述】:

我目前正在开展一个项目,该项目涉及使用我的数据库(PostgreSQL 作为 RDMS)跟踪用户及其操作,并且在尝试对每个用户的出现执行 COUNT(*) 时遇到了问题。我想要的是能够有效地计算每个用户从每个记录中出现的次数,并且还能够实现查看特定日期范围。

所以,问题是我们如何计算用户从表格内容中出现的总次数,以及我们如何计算一个日期范围内的总数。

我的尝试

您可能知道,Postgres 不能很好地支持使用索引的 COUNT(*),因此我们必须考虑其他方法来减少它查看的记录数以加快查询速度。所以我的第一种方法是创建一个表来跟踪用户有多少次与他们关联的日志消息,以及在哪一天(类似于物化视图背后的想法,但我不想不断刷新物化视图与我的计数查询)。这是我想出的:

CREATE TABLE users_counts(user varchar(65536), counter int default 0, day date);

CREATE RULE inc_user_date_count 
AS ON INSERT TO main_table 
DO ALSO UPDATE users_counts SET counter = counter + 1 
WHERE user = NEW.user AND day = DATE(NEW.date_);

每次在我的“main_table”中插入新记录时,我们都会更新当前 users_counts 表以增加日期等于新记录日期且用户名相同的记录。

p>

注意:“main_table”中的 date_ 列是时间戳,因此我必须将新记录 date_ 转换为 DATE 类型。

问题是,如果当日我的新表“users_count”中不存在用户列值,那么什么都不会更新。

这是我的问题:

如何编写规则,以便我们检查用户在当天是否存在,如果存在则增加该计数器,否则插入用户、日期和计数器为 1 的新行;

我也想知道我的方法是否有意义,或者是否有任何我没有想到的想法。随着数据库的增长,执行计数的效率越来越低,因此我想避免任何性能瓶颈。

编辑 1:我实际上可以通过创建单独的规则来解决这个问题,但我不确定这是否正确:

CREATE RULE test_insert AS ON INSERT TO main_table 
DO ALSO INSERT INTO users_counts(user, counter, day) 
SELECT NEW.user, 1, DATE(NEW.date) 
WHERE NOT EXISTS (SELECT user FROM users.log_messages WHERE user = NEW.user_);

基本上,如果用户在我的名为 user_counts 的 CACHED 表中尚不存在,则会发生插入,并且上面的第一条规则会更新计数。

我不确定的是我如何知道首先调用哪个规则,更新规则或插入..并且必须有更好的方法,我如何结合这两个规则?这可以用函数来完成吗?

【问题讨论】:

    标签: postgresql count


    【解决方案1】:

    当涉及到 count(*) 查询时,postgresql 确实是出了名的慢。但是,如果您确实有一个限制条目数量的 where 子句,则查询会快得多。如果您使用的是 postgresql 9.2 或更高版本,则此查询将与在 mysql 中一样快,因为 9.2 中添加了仅索引扫描,但最好解释一下分析您的查询以确保。

    我的解决方案有意义吗?

    如果您的解释分析表明未使用仅索引扫描,则非常重要。基于触发器的解决方案(例如您采用的解决方案)得到广泛使用。但是当你意识到初始状态的问题出现时(无论是更新还是插入)。

    先调用哪个规则

    同一张桌子上的多个规则和相同的事件类型应用于 姓名字母顺序。

    来自http://www.postgresql.org/docs/9.1/static/sql-createrule.html 这同样适用于触发器。如果您希望首先执行特定规则,请更改它的名称,使其按字母顺序排在前面。

    如何结合这两条规则?

    一种解决方案是修改您的规则以执行upsert(查看该页面底部的示例 upsert )。另一种是用初始值填充计数器表。诀窍是同时创建触发器以避免错误。这个blog post 解释得很好。

    虽然初始设置会很慢,但每个单独的插入可能会更快。两个相反的因素是 WHERE NOT EXISTS 查询的缓慢与捕获异常的开销。

    提示:包含 EXCEPTION 子句的块明显更多 进出比没有的街区贵。因此,不要 无需使用 EXCEPTION。

    获取上面链接的 postgresql 文档页面。

    【讨论】:

    • 非常好的答案,我很感激。我确实看到,通过添加适当的索引,仅索引扫描的 COUNT(*) 速度更快,问题是我的一个用例是查看日期范围并从中返回用户总数。好吧,假设我们正在查看一个有 100 万条记录的月份,当我需要的唯一谓词是日期时,计数操作仍然很慢,并且索引有帮助但不够快。
    • 虽然非常“hacky”,但我想我可以选择有目的地按字母顺序排列规则,以便我们先尝试更新,然后插入,因为先插入后更新会导致双倍增量。但我确实更喜欢你展示的“upsert”功能:)
    • 很高兴做出贡献。猜猜在这种情况下你别无选择,只能使用基于触发器/规则的方法。您也许可以创建您的规则,例如 increment_counter 和 initialize_counter,并且仍然保持规则名称有意义。
    • 您还让我注意到的一件事是,一旦应用了更多过滤,我对 count(*) 的查询比仅过滤日期要快得多。这是因为我的数据库由日志组成。将我的“缓存”表用于除日期之外没有过滤的查询,并在应用足够的过滤时使用我的普通查询是否有意义?这在我的项目中很容易确定,所以我可以做到。
    • 如果您的意思是只计算总行数而不进行任何过滤,并且一个非常好的估计结果是可以的,您可以这样做:select reltuples from pg_class where relname = 'your_table_name'(注意分区在使用时不起作用,并且在视图上不起作用)
    猜你喜欢
    • 1970-01-01
    • 2016-11-04
    • 2016-07-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-02
    • 2011-11-27
    • 2022-01-24
    相关资源
    最近更新 更多