在我的应用程序中,我有一个“季节”的概念,随着时间的推移不断变化。所有实体都与某个季节有关。所有实体都有基于季节的指数以及其他领域的一些指数。当季节变化发生时,postgresql决定使用基于季节索引的过滤扫描计划而不是更具体的字段索引。在赛季初,这样的决定的计划成本很少,所以没关系,但问题是 - 赛季的变化让很多用户在赛季初就来了,所以基于postgresql扫描的查询计划变得非常快 - 它只扫描新季节中的所有实体,并过滤目标项目。在第一次自动分析后,postgres决定使用一个好的计划,但是由于争用而自动分析非常缓慢,我认为它就像一个雪球 - 请求越多,争用就越多,因为计划不好因此自动分析工作缓慢慢慢地。自动分析工作的最长时间是上周大约一个小时,这成了一个真正的问题。我知道postgresql架构师决定禁用选择查询中使用的索引的可能性,但是解决我的问题的最佳方法是什么呢?
只是为了澄清,这里是一个DDL,一个“慢”查询并解释自动分析前后的结果。
DDL
CREATE TABLE race_results (
id INTEGER PRIMARY KEY NOT NULL DEFAULT nextval('race_results_id_seq'::regclass),
user_id INTEGER NOT NULL,
opponent_id INTEGER,
season_id INTEGER NOT NULL,
type RACE_TYPE NOT NULL DEFAULT 'battle'::race_type,
elo_delta INTEGER NOT NULL,
opponent_elo_delta INTEGER NOT NULL DEFAULT 0,
);
CREATE INDEX race_results_type_user_id_index ON race_results USING BTREE (season_id, type, user_id);
CREATE INDEX race_results_type_opponent_id_index ON race_results USING BTREE (season_id, type, opponent_id);
CREATE INDEX race_results_opponent_id_index ON race_results USING BTREE (opponent_id);
CREATE INDEX race_results_user_id_index ON race_results USING BTREE (user_id);
查询
SELECT 1000 + COALESCE(SUM(CASE WHEN user_id = 6446 THEN elo_delta ELSE opponent_elo_delta END), 0)
FROM race_results
WHERE type = 'battle' :: race_type AND (user_id = 6446 OR opponent_id = 6446) AND
season_id = current_season_id()
自动分析前的解释结果(如您所见,过滤器已经删除了超过一千个项目,很快每个请求就会变成数十万个)
自动分析后解释分析的结果(现在postgres决定使用正确的索引,不再需要过滤,但问题是 - 自动分析花费的时间太长,部分原因是前一张图片中无效索引选择的争用)
ps:现在我解决问题只是在季节变化后10秒后关闭应用程序服务器,以便postgres获取新数据并开始自动分析,然后在自动分析结束时打开它,但这样的解决方案涉及停机时间,这是不可取的,总体而言看起来很奇怪
答案 0 :(得分:0)
最后我找到了解决方案。它并不完美,我不会把它标记为最好的,但它有效,可以帮助别人。
而不是季节,类型和用户/对手ID的索引,我现在有索引
CREATE INDEX race_results_type_user_id_index ON race_results USING BTREE (user_id,season_id, type);
CREATE INDEX race_results_type_opponent_id_index ON race_results USING BTREE (opponent_id,season_id, type);
出现了一个问题 - 我在其他查询中需要和索引季节,但是当我添加索引
时CREATE INDEX race_results_season_index ON race_results USING BTREE (season_id);
计划者试图再次使用它而不是那些正确的指数,并重复整个情况。我所做的只是增加了一个字段:'season_id_clone',它包含与'season_id'相同的数据,并且我对它做了一个索引。现在,当我需要根据季节过滤某些内容(不包括第一篇文章中的查询)时,我在查询中使用了season_id_clone。我知道这很奇怪,但我没有找到更好的东西。