当表很小时,此查询的合理时间。我正在试图找出瓶颈是什么,但我不确定如何分析EXPLAIN
的结果。
SELECT
COUNT(*)
FROM performance_analyses
INNER JOIN total_sales ON total_sales.id = performance_analyses.total_sales_id
WHERE
(size > 0) AND
total_sales.customer_id IN (
SELECT customers.id FROM customers WHERE customers.active = 't'
AND customers.visible = 't' AND customers.organization_id = 3
) AND
total_sales.product_category_id IN (
SELECT product_categories.id FROM product_categories
WHERE product_categories.organization_id = 3
) AND
total_sales.period_id = 193;
我已经尝试了INNER JOIN'ing customers
和product_categories
表并进行INNER SELECT的方法。两者都有相同的时间。
这是EXPLAIN的链接:https://explain.depesz.com/s/9lhr
Postgres版本:
在x86_64-unknown-linux-gnu上的PostgreSQL 9.4.5,由gcc(GCC)4.8.2 20140120(Red Hat 4.8.2-16)编译,64位
表和索引:
CREATE TABLE total_sales (
id serial NOT NULL,
value double precision,
start_date date,
end_date date,
product_category_customer_id integer,
created_at timestamp without time zone,
updated_at timestamp without time zone,
processed boolean,
customer_id integer,
product_category_id integer,
period_id integer,
CONSTRAINT total_sales_pkey PRIMARY KEY (id)
);
CREATE INDEX index_total_sales_on_customer_id ON total_sales (customer_id);
CREATE INDEX index_total_sales_on_period_id ON total_sales (period_id);
CREATE INDEX index_total_sales_on_product_category_customer_id ON total_sales (product_category_customer_id);
CREATE INDEX index_total_sales_on_product_category_id ON total_sales (product_category_id);
CREATE INDEX total_sales_product_category_period ON total_sales (product_category_id, period_id);
CREATE INDEX ts_pid_pcid_cid ON total_sales (period_id, product_category_id, customer_id);
CREATE TABLE performance_analyses (
id serial NOT NULL,
total_sales_id integer,
status_id integer,
created_at timestamp without time zone,
updated_at timestamp without time zone,
size double precision,
period_size integer,
nominal_variation double precision,
percentual_variation double precision,
relative_performance double precision,
time_ago_max integer,
deseasonalized_series text,
significance character varying,
relevance character varying,
original_variation double precision,
last_level double precision,
quantiles text,
range text,
analysis_method character varying,
CONSTRAINT performance_analyses_pkey PRIMARY KEY (id)
);
CREATE INDEX index_performance_analyses_on_status_id ON performance_analyses (status_id);
CREATE INDEX index_performance_analyses_on_total_sales_id ON performance_analyses (total_sales_id);
CREATE TABLE product_categories (
id serial NOT NULL,
name character varying,
organization_id integer,
created_at timestamp without time zone,
updated_at timestamp without time zone,
external_id character varying,
CONSTRAINT product_categories_pkey PRIMARY KEY (id)
);
CREATE INDEX index_product_categories_on_organization_id ON product_categories (organization_id);
CREATE TABLE customers (
id serial NOT NULL,
name character varying,
external_id character varying,
region_id integer,
organization_id integer,
created_at timestamp without time zone,
updated_at timestamp without time zone,
active boolean DEFAULT false,
visible boolean DEFAULT false,
segment_id integer,
"group" boolean,
group_id integer,
ticket_enabled boolean DEFAULT true,
CONSTRAINT customers_pkey PRIMARY KEY (id)
);
CREATE INDEX index_customers_on_organization_id ON customers (organization_id);
CREATE INDEX index_customers_on_region_id ON customers (region_id);
CREATE INDEX index_customers_on_segment_id ON customers (segment_id);
行数:
您的查询,重写和100%等效:
SELECT count(*)
FROM product_categories pc
JOIN customers c USING (organization_id)
JOIN total_sales ts ON ts.customer_id = c.id
JOIN performance_analyses pa ON pa.total_sales_id = ts.id
WHERE pc.organization_id = 3
AND c.active -- boolean can be used directly
AND c.visible
AND ts.product_category_id = pc.id
AND ts.period_id = 193
AND pa.size > 0;
另一个答案建议将所有条件移动到FROM
列表中的join子句和order表中。这可能适用于具有相对原始的查询规划器的某个其他RDBMS。虽然它对Postgres也没有影响,但它对查询的性能也没有影响 - 假设默认服务器配置。 The manual:
显式内连接语法(
INNER JOIN
,CROSS JOIN
或unadornedJOIN
)在语义上与在FROM
中列出输入关系相同,因此它不约束连接顺序。
大胆强调我的。还有更多,请阅读手册。
关键设置是join_collapse_limit
(默认为8)。 Postgres查询计划程序将重新排列您的4个表,无论您如何安排表格以及是否以WHERE
或JOIN
条款编写条件,它都希望它最快。没有任何区别。 (对于无法自由重新排列的其他类型的连接,情况也是如此。)
重要的一点是,这些不同的连接可能性在语义上具有相同的结果,但可能会有非常不同的执行成本。因此,规划人员将探索所有这些以尝试找到最有效的查询计划。
有关:
最后,WHERE id IN (<subquery>)
通常不等同于连接。对于右侧的重复匹配值,它不会在左侧乘以行。对于查询的其余部分,子查询的列不可见。连接可以将具有重复值的行相乘,并且列是可见的。
在这两种情况下,您的简单子查询都会挖出一个唯一的列,因此在这种情况下没有任何有效差异 - 除了IN (<subquery>)
通常(至少有点)更慢且更冗长。使用连接。
product_categories
有34排。除非您计划添加更多内容,否则索引对此表没有帮助。顺序扫描总是更快。下降
index_product_categories_on_organization_id
。
customers
有6,970行。索引开始有意义。但根据EXPLAIN
输出,您的查询使用了4,988个。只有一个index-only scan在索引上比表格宽得多,可能会有所帮助。假设WHERE active AND visible
是常量谓词,我建议使用部分多列索引:
CREATE INDEX index_customers_on_organization_id ON customers (organization_id, id)
WHERE active AND visible;
我附加了id
以允许仅索引扫描。该列在此查询的索引中无用。
total_sales
有7,104,441行。索引非常重要。我建议:
CREATE INDEX index_total_sales_on_product_category_customer_id
ON total_sales (period_id, product_category_id, customer_id, id)
再次,旨在进行仅索引扫描。这是最重要的一个。
您可以删除完全冗余的索引
index_total_sales_on_product_category_id
。
performance_analyses
有1,012,346行。索引非常重要。我会建议使用条件size > 0
的另一个部分索引:
CREATE INDEX index_performance_analyses_on_status_id
ON performance_analyses (total_sales_id)
WHERE pa.size > 0;
然而:
筛选删除的行:0“
好像这个条件没有用处?有size > 0
的行是不是真的?
创建这些索引后,您需要ANALYZE
表。
一般来说,我看到许多不好的估计。 Postgres低估了几乎每一步返回的行数。我们看到的嵌套循环对于更少的行会更好。除非这不太可能巧合,否则您的表统计数据已经过时了。您需要访问autovacuum的设置,也可能需要访问两个大表performance_analyses
和total_sales
的每个表设置。
你已经运行了VACUUM
和ANALYZE
,这使查询变慢,according to your comment。这没有多大意义。我会在这两张桌子上运行一次VACUUM FULL
(如果你能买得起专属锁)。否则试试pg_repack
。
有了所有可疑的统计数据和糟糕的计划,我会考虑在你的数据库上运行一个完整的vacuumdb -fz yourdb
。这会在原始条件下重写所有表和索引,但定期使用并不好。它也很昂贵,会长时间锁定你的数据库!
在此期间,还要查看数据库的成本设置。有关:
虽然理论上优化器应该能够做到这一点,但我经常发现这些变化可以大大提高性能:
where id in (select ...)
)from
子句中的表的引用,以便在每次连接时返回最少的行,尤其是第一个表的条件(在where子句中)应该是最严格的(并且应该使用索引)on
条件试试这个(添加别名以提高可读性):
select count(*)
from total_sales ts
join product_categories pc on ts.product_category_id = pc.id and pc.organization_id = 3
join customers c on ts.customer_id = c.id and c.organization_id = 3
join performance_analyses pa on ts.id = pa.total_sales_id and pa.size > 0
where ts.period_id = 193
您需要创建此索引以获得最佳性能(以允许对total_sales进行仅索引扫描):
create index ts_pid_pcid_cid on total_sales(period_id, product_category_id, customer_id)
这种方法首先将数据缩小到一个时期,因此它将在未来扩展(保持大致不变),因为每个时期的销售数量将大致不变。
这些估计并不准确。 Postgres的计划程序使用错误的嵌套循环 - 尝试通过语句set enable_nestloop to off
惩罚nest_loop。