【问题标题】:How to speed up the query in PostgreSQL如何加快 PostgreSQL 中的查询
【发布时间】:2015-09-14 23:05:34
【问题描述】:

我在 PostgreSQL 中有一个带有大数据的数据库(现在它大约是 46 GB,并且数据库将继续增长)。我在常用列上创建了索引并调整了配置文件:

shared_buffers = 1GB
temp_buffers = 256MB
work_mem = 512MB

但是这个查询还是很慢:

select distinct us_category_id as cat, count(h_user_id) as res from web_hits 
inner join users on h_user_id = us_id 
where (h_datetime)::date = ('2015-06-26')::date and us_category_id != ''
group by us_category_id

解释分析:

HashAggregate (cost=2870958.72..2870958.93 rows=21 width=9) (actual time=899141.683..899141.683 rows=0 loops=1)

Group Key: users.us_category_id, count(web_hits.h_user_id)
-> HashAggregate (cost=2870958.41..2870958.62 rows=21 width=9) (actual time=899141.681..899141.681 rows=0 loops=1)

Group Key: users.us_category_id
 -> Hash Join (cost=5974.98..2869632.11 rows=265259 width=9) (actual time=899141.679..899141.679 rows=0 loops=1)

Hash Cond: ((web_hits.h_user_id)::text = (users.us_id)::text)
-> Seq Scan on web_hits (cost=0.00..2857563.80 rows=275260 width=7) (actual time=899141.676..899141.676 rows=0 loops=1)
-> Seq Scan on web_hits (cost=0.00..2857563.80 rows=275260 width=7) (actual time=899141.676..899141.676 rows=0 loops=1)
Filter: ((h_datetime)::date = '2015-06-26'::date)

Rows Removed by Filter: 55051918
-> Hash (cost=4292.99..4292.99 rows=134559 width=10) (never executed)
-> Seq Scan on users (cost=0.00..4292.99 rows=134559 width=10) (never executed)
Filter: ((us_category_id)::text <> ''::text)

"Planning time: 1.309 ms"
"Execution time: 899141.789 ms"

日期已更改。 如何加快查询速度?

创建表和索引

CREATE TABLE web_hits (
  h_id integer NOT NULL DEFAULT nextval('w_h_seq'::regclass),
  h_user_id character varying,
  h_datetime timestamp without time zone,
  h_db_id character varying,
  h_voc_prefix character varying,
  ...
  h_bot_chek integer, -- 1-бот...
  CONSTRAINT w_h_pk PRIMARY KEY (h_id)
);
ALTER TABLE web_hits OWNER TO postgres;
COMMENT ON COLUMN web_hits.h_bot_chek IS '1-бот, 0-не бот';

CREATE INDEX h_datetime ON web_hits (h_datetime);
CREATE INDEX h_db_index ON web_hits (h_db_id COLLATE pg_catalog."default");
CREATE INDEX h_pref_index ON web_hits (h_voc_prefix COLLATE pg_catalog."default" text_pattern_ops);
CREATE INDEX h_user_index ON web_hits (h_user_id text_pattern_ops);

 CREATE TABLE users (
  us_id character varying NOT NULL,
  us_category_id character varying,
  ...
  CONSTRAINT user_pk PRIMARY KEY (us_id),
  CONSTRAINT cities_users_fk FOREIGN KEY (us_city_home)
      REFERENCES cities (city_id),
  CONSTRAINT countries_users_fk FOREIGN KEY (us_country_home)
      REFERENCES countries (country_id),
  CONSTRAINT organizations_users_fk FOREIGN KEY (us_institution_id)
      REFERENCES organizations (org_id),
  CONSTRAINT specialities_users_fk FOREIGN KEY (us_speciality_id)
      REFERENCES specialities (speciality_id),
  CONSTRAINT us_affiliation FOREIGN KEY (us_org_id)
      REFERENCES organizations (org_id),
  CONSTRAINT us_category FOREIGN KEY (us_category_id)
      REFERENCES categories (cat_id),
  CONSTRAINT us_reading_room FOREIGN KEY (us_reading_room_id)
      REFERENCES reading_rooms (rr_id)
);
ALTER TABLE users OWNER TO sveta;
COMMENT ON COLUMN users.us_type IS '0-аноним, 1-читатель, 2-удаленный';

CREATE INDEX us_cat_index ON users (us_category_id);
CREATE INDEX us_user_index ON users (us_id text_pattern_ops);

【问题讨论】:

  • 请注意,您可以删除 DISTINCT 关键字,因为由于您的 GROUP BY,结果已经很明显了。
  • 请发布表和索引定义。
  • 您能详细说明您已经建立的索引吗?这两个表看起来都被 seq scan 访问了。
  • 我从您的设置中删除了噪音(默认设置)。另一方面,重要信息丢失。考虑postgresql-performance 的标签信息中的说明。为什么postgres 拥有一张表,sveta 拥有另一张表?您对多个 ID 列使用字符数据类型而不是普通的 integer(或 bigint)有什么特殊原因?
  • 为什么web_hits.h_user_id 没有定义NOT NULL?列中是否有 NULL 值?如果是,您打算如何计算这些?从web_hits.h_user_idusers. us_id似乎真的应该有一个FK约束......

标签: sql windows postgresql postgresql-performance


【解决方案1】:

问题中缺少基本信息。我将根据有根据的猜测来回答部分问题。 web_hits.h_user_id 有时为 NULL,就像您在评论中添加的那样。

查询

基本上,查询在任何情况下都可以简化/改进:

SELECT u.us_category_id AS cat, count(*) AS res
FROM   users    u
JOIN   web_hits w ON w.h_user_id = u.us_id
WHERE  w.h_datetime >= '2015-06-26 0:0'::timestamp
AND    w.h_datetime <  '2015-06-27 0:0'::timestamp
AND    w.h_user_id IS NOT NULL  -- remove irrelevant rows, match index
AND    u.us_category_id <> ''
GROUP  BY 1;
  • DISTINCT 显然是不必要的,因为您已经group by us_category_id(如@Gordon already mentioned)。

  • 设置条件sargable,以便可以使用索引:

  • 由于您已加入列w.h_user_id,因此从逻辑上讲,结果行在此列中为NOT NULLcount(*) 在这种情况下是等效的,而且速度更快。

  • 条件h_user_id IS NOT NULL 似乎是多余的,因为无论如何在JOIN 中消除了NULL,但它允许使用匹配条件的部分索引(见下文)。

  • users.us_id(因此web_hits.h_user_id)可能不应该具有数据类型varcharcharacter varying)。对于大型表中的 PK / FK 列,这是一种低效的数据类型。使用像int or bigint(或uuid,如果必须)这样的数字数据类型。 us_category_id: 的类似注意事项应该是 integer 或相关的。

  • 标准 SQL 不等式运算符是 &lt;&gt;。使用它来代替同样受支持的!=

  • 使用表格限定来避免歧义 - 在任何情况下,让您的查询在公共论坛上向读者清楚。

优化

进一步假设:

  • users.us_category_id &lt;&gt; '' 适用于大多数行。
  • 包含web_hits.h_user_id IS NOT NULL 的大多数或所有行都被计算在内。

那么这会更快,但是:

SELECT u.us_category_id AS cat, sum(ct) AS res
FROM   users u
JOIN  (
   SELECT h_user_id, count(*) AS ct
   FROM   web_hits
   WHERE  h_datetime >= '2015-06-26 0:0'::timestamp
   AND    h_datetime <  '2015-06-27 0:0'::timestamp
   AND    h_user_id IS NOT NULL  -- remove irrelevant rows, match index
   GROUP  BY 1
   ) w ON w.h_user_id = u.us_id
AND    u.us_category_id <> ''
GROUP  BY 1;

索引

无论哪种方式,partial indexes 最适合您的情况:

1.

CREATE INDEX wh_usid_datetime_idx ON web_hits(h_user_id, h_datetime)
WHERE  h_user_id IS NOT NULL;

从索引中删除web_hits.h_user_id IS NULL 所在的行。

按该顺序,而不是建议的相反方式。详细解释:

2.

CREATE INDEX us_usid_cat_not_empty_idx ON users(us_id)
WHERE  us_category_id <> '';

这会小很多,因为我们不会在索引中存储可能很长的varcharus_category_id - 无论如何我们都不需要这种情况。我们只需要知道它是&lt;&gt; ''。如果您有 integer 列,则此考虑将不适用。

我们还排除了us_category_id 中带有''NULL 的行,从而使索引更小。

您必须权衡特殊索引的维护成本和它们的好处。如果您经常使用匹配条件运行查询,它会付费,否则可能不会,而且更通用的索引总体上可能会更好。


当然,performance optimization 上的所有常见建议也适用。

坦率地说,关于您的查询并没有太多正确,而且您的设置中有许多项目是可疑的。处理像你这样的大桌子,你可能会考虑专业的帮助。

【讨论】:

  • 不幸的是,web_hits.h_user_id 可以为 NULL 或具有字母值。但要不然你给了我详细的资料,所以非常感谢!
  • @Svetlana:在评论中更新了我对您更新的回答。请记住在下一个问题中提供所有相关信息。
【解决方案2】:

首先,不需要区分:

select u.us_category_id as cat, count(h_user_id) as res
from web_hits h inner join
     users u
     on h.h_user_id = u.us_id 
where (h.h_datetime)::date = '2015-06-26'::date and
      u.us_category_id <> ''
group by u.us_category_id

其次,您要删除列上的转化。所以:

select u.us_category_id as cat, count(h_user_id) as res
from web_hits h inner join
     users u
     on h.h_user_id = u.us_id 
where (h.h_datetime >= '2015-06-26' and h.h_datetime < '2015-06-27) and
      u.us_category_id <> ''
group by u.us_category_id;

然后,以下索引应该有助于查询:web_hits(h_datetime, h_user_id)。在users(us_id, us_category_id) 上建立索引也可能是有益的。

【讨论】:

  • Сonversion "::date" 无法删除,因为 h_datetime 的格式为 yyyyMMdd dd:mm:ss
  • h_datetime的数据类型是什么?
  • 没有时区的时间戳
  • 那你就不用担心格式了。时间戳本质上是一个浮点数。各种客户端以各种方式显示它。
  • @DanBracuk:实际上,timestamp 是一个 integer,在现代 Postgres 内部代表 6 个小数位:stackoverflow.com/a/9576170/939860
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-10-16
  • 1970-01-01
  • 2012-11-22
  • 2018-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-12
相关资源
最近更新 更多