【问题标题】:PostgreSQL Simple JOIN very slowPostgreSQL Simple JOIN 很慢
【发布时间】:2013-01-25 11:11:59
【问题描述】:

我有一个简单的查询和两个表:

drilldown

CREATE SEQUENCE drilldown_id_seq;

CREATE TABLE drilldown (
    transactionid bigint NOT NULL DEFAULT nextval('drilldown_id_seq'),
    userid bigint NOT NULL default 0 REFERENCES users(id),
    pathid bigint NOT NULL default 0,
    reqms bigint NOT NULL default 0,
    quems bigint NOT NULL default 0,
    clicktime timestamp default current_timestamp,
    PRIMARY KEY(transactionid)
);

ALTER SEQUENCE drilldown_id_seq OWNED BY drilldown.transactionid;

CREATE INDEX drilldown_idx1 ON drilldown (clicktime);

querystats

CREATE SEQUENCE querystats_id_seq;

CREATE TABLE querystats (
    id bigint NOT NULL DEFAULT nextval('querystats_id_seq'),
    transactionid bigint NOT NULL default 0 REFERENCES drilldown(transactionid),
    querynameid bigint NOT NULL default 0 REFERENCES queryname(id),
    queryms bigint NOT NULL default 0,
    PRIMARY KEY(id)
);

ALTER SEQUENCE querystats_id_seq OWNED BY querystats.id;

CREATE INDEX querystats_idx1 ON querystats (transactionid);
CREATE INDEX querystats_idx2 ON querystats (querynameid);

drilldown有150万条记录,querystats有1000万条记录;当我在两者之间加入时,就会出现问题。

查询

explain analyse
select avg(qs.queryms)
  from querystats qs
  join drilldown d on (qs.transactionid=d.transactionid)
  where querynameid=1;

查询计划

聚合(成本=528596.96..528596.97 行=1 宽度=8)(实际时间=5213.154..5213.154 行=1 循环=1) -> Hash Join (cost=274072.53..518367.59 rows=4091746 width=8) (实际时间=844.087..3528.788 rows=4117717 loops=1) 哈希条件:(qs.transactionid = d.transactionid) -> 查询统计 qs 上的位图堆扫描(成本=88732.62..210990.44 行=4091746 宽度=16)(实际时间=309.502..1321.029 行=4117717 循环=1) 重新检查条件:(querynameid = 1) -> querystats_idx2 上的位图索引扫描(成本=0.00..87709.68 行=4091746 宽度=0)(实际时间=307.916..307.916 行=4117718 循环=1) 索引条件:(querynameid = 1) -> 哈希(成本=162842.29..162842.29 行=1371250 宽度=8)(实际时间=534.065..534.065 行=1372574 循环=1) 存储桶:4096 批次:64 内存使用量:850kB -> 使用drilldown_pkey 对drilldown d 进行索引扫描(成本=0.00..162842.29 行=1371250 宽度=8)(实际时间=0.015..364.657 行=1372574 循环=1) 总运行时间:5213.205 毫秒 (11 行)

我知道我可以为 PostgreSQL 调整一些调整参数,但我想知道的是我正在做的查询是连接两个表的最佳方式吗?

或者可能是某种 INNER JOIN?我只是不确定。

任何指针表示赞赏!

编辑

database#\d drilldown
                                       Table "public.drilldown"
    Column     |            Type             |                       Modifiers                        
---------------+-----------------------------+--------------------------------------------------------
 transactionid | bigint                      | not null default nextval('drilldown_id_seq'::regclass)
 userid        | bigint                      | not null default 0
 pathid        | bigint                      | not null default 0
 reqms         | bigint                      | not null default 0
 quems         | bigint                      | not null default 0
 clicktime     | timestamp without time zone | default now()
Indexes:
    "drilldown_pkey" PRIMARY KEY, btree (transactionid)
    "drilldown_idx1" btree (clicktime)
Foreign-key constraints:
    "drilldown_userid_fkey" FOREIGN KEY (userid) REFERENCES users(id)
Referenced by:
    TABLE "querystats" CONSTRAINT "querystats_transactionid_fkey" FOREIGN KEY (transactionid) REFERENCES drilldown(transactionid)

database=# \d querystats
                            Table "public.querystats"
    Column     |  Type  |                        Modifiers                        
---------------+--------+---------------------------------------------------------
 id            | bigint | not null default nextval('querystats_id_seq'::regclass)
 transactionid | bigint | not null default 0
 querynameid   | bigint | not null default 0
 queryms       | bigint | not null default 0
Indexes:
    "querystats_pkey" PRIMARY KEY, btree (id)
    "querystats_idx1" btree (transactionid)
    "querystats_idx2" btree (querynameid)
Foreign-key constraints:
    "querystats_querynameid_fkey" FOREIGN KEY (querynameid) REFERENCES queryname(id)
    "querystats_transactionid_fkey" FOREIGN KEY (transactionid) REFERENCES drilldown(transactionid)

所以这里是请求的两个表和版本

PostgreSQL 9.1.7 on x86_64-unknown-linux-gnu, compiled by gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3, 64-bit

所以这个查询正在做的是从每个查询类型 (querynameid) 的 queryms 的所有行值中获取平均值

            name            |         current_setting          |        source        
----------------------------+----------------------------------+----------------------
 application_name           | psql                             | client
 client_encoding            | UTF8                             | client
 DateStyle                  | ISO, MDY                         | configuration file
 default_text_search_config | pg_catalog.english               | configuration file
 enable_seqscan             | off                              | session
 external_pid_file          | /var/run/postgresql/9.1-main.pid | configuration file
 lc_messages                | en_US.UTF-8                      | configuration file
 lc_monetary                | en_US.UTF-8                      | configuration file
 lc_numeric                 | en_US.UTF-8                      | configuration file
 lc_time                    | en_US.UTF-8                      | configuration file
 log_line_prefix            | %t                               | configuration file
 log_timezone               | localtime                        | environment variable
 max_connections            | 100                              | configuration file
 max_stack_depth            | 2MB                              | environment variable
 port                       | 5432                             | configuration file
 shared_buffers             | 24MB                             | configuration file
 ssl                        | on                               | configuration file
 TimeZone                   | localtime                        | environment variable
 unix_socket_directory      | /var/run/postgresql              | configuration file
(19 rows)

我看到 enable_seqscan=off,我没有动任何设置,这是完全默认的安装。

更新

我对以下 cmets 进行了一些更改,结果如下。

explain analyse SELECT (SELECT avg(queryms) AS total FROM querystats WHERE querynameid=3) as total FROM querystats qs JOIN drilldown d ON (qs.transactionid=d.transactionid) WHERE qs.querynameid=3 limit 1;
                                                                       QUERY PLAN                                                                        
---------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=196775.99..196776.37 rows=1 width=0) (actual time=2320.876..2320.876 rows=1 loops=1)
   InitPlan 1 (returns $0)
     ->  Aggregate  (cost=196775.94..196775.99 rows=1 width=8) (actual time=2320.815..2320.815 rows=1 loops=1)
           ->  Bitmap Heap Scan on querystats  (cost=24354.25..189291.69 rows=2993698 width=8) (actual time=226.516..1144.690 rows=2999798 loops=1)
                 Recheck Cond: (querynameid = 3)
                 ->  Bitmap Index Scan on querystats_idx  (cost=0.00..23605.83 rows=2993698 width=0) (actual time=225.119..225.119 rows=2999798 loops=1)
                       Index Cond: (querynameid = 3)
   ->  Nested Loop  (cost=0.00..1127817.12 rows=2993698 width=0) (actual time=2320.876..2320.876 rows=1 loops=1)
         ->  Seq Scan on drilldown d  (cost=0.00..76745.10 rows=1498798 width=8) (actual time=0.009..0.009 rows=1 loops=1)
         ->  Index Scan using querystats_idx on querystats qs  (cost=0.00..0.60 rows=2 width=8) (actual time=0.045..0.045 rows=1 loops=1)
               Index Cond: ((querynameid = 3) AND (transactionid = d.transactionid))
 Total runtime: 2320.940 ms
(12 rows)

【问题讨论】:

  • 感谢您展示您的查询计划; +1 好问题。最好还包括您的 Pg 版本。还可以考虑在来自psql 的感兴趣的表上显示\d 输出,这样我们就可以看到存在哪些索引、表定义等。
  • 我回家后会添加你需要的东西,谢谢。

标签: performance postgresql join


【解决方案1】:

它的行为就像您设置了enable_seqscan = off,因为它使用索引扫描来填充哈希表。除非作为诊断步骤,否则切勿关闭任何计划器选项,如果您正在显示计划,请显示使用的任何选项。这可以运行以显示很多有用的信息:

SELECT version();
SELECT name, current_setting(name), source
  FROM pg_settings
  WHERE source NOT IN ('default', 'override');

如果您告诉我们运行时环境,尤其是机器上的 RAM 量、存储系统的外观以及数据库的大小(或者更好的是,活动数据集 数据库中经常引用的数据)。

作为粗略的细分,5.2 秒细分为:

  1. 1.3 秒即可找到符合您的选择标准的 4,117,717 querystats 行。
  2. 2.3 秒随机匹配 drilldown 记录。
  3. 1.6 秒通过 4,117,717 行并计算平均值。

因此,尽管您似乎削弱了它使用最快计划的能力,但它只需要 1.26 微秒(百万分之一秒)即可找到每一行,将其连接到另一行,然后计算平均的。这在绝对基础上并不算太糟糕,但您几乎可以肯定得到一个稍微快一点的计划。

首先,如果您使用的是 x 小于 3 的 9.2.x,请立即升级到 9.2.3。在最近的版本中修复了某些类型的计划的性能回归,这可能会影响此查询。一般情况下,try to stay up-to-date on minor releases(其中版本号更改超过第二个点)。

您可以在一个会话中测试不同的计划,方法是在该连接上设置计划因素并运行您的查询(或在其上使用EXPLAIN)。试试这样的:

SET seq_page_cost = 0.1;
SET random_page_cost = 0.1;
SET cpu_tuple_cost = 0.05;
SET effective_cache_size = '3GB'; -- actually use shared_buffers plus OS cache

确保所有enable_ 设置都是on

【讨论】:

  • enable_seqscan 很受欢迎!
【解决方案2】:

您在问题中声称:

我看到 enable_seqscan=off,我没有动任何设置,这是完全默认的安装。

相比之下,pg_settings 的输出告诉我们:

enable_seqscan |关闭 |会话

意思是,您在会话中设置enable_seqscan = off 。这里没有加起来。

运行

SET enable_seqscan = on;

RESET enable_seqscan;

断言:

SHOW enable_seqscan;

另外,对于拥有数百万条记录的数据库,您对 shared_buffers 的设置太低了24MB 似乎是 Ubuntu 开箱即用的保守设置。您需要编辑配置文件以供认真使用!我引用手册:

如果您有一个具有 1GB 或更多 RAM 的专用数据库服务器,则 shared_buffers 的合理起始值是系统内存的 25%。

所以编辑您的postgresql.conf 文件以增加值并重新加载。
然后再次尝试查询,了解enable_seqscan 是如何关闭的。

【讨论】:

  • 在您发布第一篇文章后,我确实将其更改为 enable_seqscan = on,至于我“声称”的内容,我没有触及此安装的任何配置设置。感谢您的帮助
【解决方案3】:

在这个查询中

select avg(qs.queryms) 
from querystats qs 
join drilldown d 
  on (qs.transactionid=d.transactionid) 
where querynameid=1;

您没有使用“向下钻取”表中的任何列。由于外键约束保证“querystats”中的每个“transactionid”在“drilldown”中都有一行,我认为连接不会做任何有用的事情。除非我遗漏了什么,否则您的查询相当于

select avg(qs.queryms) 
from querystats qs 
where querynameid=1;

根本没有加入。只要“querynameid”上有一个索引,您就应该获得不错的性能。

【讨论】:

  • 我实际上使用的是来自向下钻取的列,我删除了它,但它会是 d.transactionid 加入另一个表。我已经排除了这个,因为我把它作为一个问题消除了。减速只会发生在这个连接上,否则它会立即出现在您命名的第二个查询中。
  • @MichaelM:所以请给出一张完整的图片(在你的问题中)。过度简化隐藏了你的实际问题。您提供的查询返回单行。
  • 我已经更新了我的问题:是的,这一切都应该返回。
【解决方案4】:

当你不加入时,avg(qs.queryms) 执行一次。

当您执行连接时,您执行avg(qs.queryms) 的次数与连接生成的行数一样多。

如果您总是对单个 querynameid 感兴趣,请尝试将 avg(qs.queryms) 放入子选择中:

SELECT 
    (SELECT avg(queryms) FROM querystats WHERE querynameid=1) 
FROM querystats qs 
JOIN drilldown d ON (qs.transactionid=d.transactionid) 
WHERE qs.querynameid=1;

【讨论】:

  • 我不这么认为。表“drilldown”位于与“querystats”的 1:M 关系的一侧。无论有没有加入,您都会在“querystats”中获得所有相关行的平均值,而“drilldown”没有任何贡献。
【解决方案5】:

对我来说,querystats 表看起来像一个 fat 联结表。在这种情况下:省略代理键,并使用自然(复合)键(两个组件都不能为空)并添加 reversed 复合索引。 (单独的索引是没用的,FK 约束会自动为你生成它们)

-- CREATE SEQUENCE querystats_id_seq;

CREATE TABLE querystats (
    -- id bigint NOT NULL DEFAULT nextval('querystats_id_seq'),
    transactionid bigint NOT NULL default 0 REFERENCES drilldown(transactionid),
    querynameid bigint NOT NULL default 0 REFERENCES queryname(id),
    queryms bigint NOT NULL default 0,
    PRIMARY KEY(transactionid,querynameid )
);

-- ALTER SEQUENCE querystats_id_seq OWNED BY querystats.id;

--CREATE INDEX querystats_idx1 ON querystats (transactionid);
-- CREATE INDEX querystats_idx2 ON querystats (querynameid);
CREATE UNIQUE INDEX querystats_alt ON querystats (querynameid, transactionid);

【讨论】:

  • 这将不起作用,因为此表中的 querynameid 和 transactionid 可以有重复的 id
猜你喜欢
  • 1970-01-01
  • 2019-08-25
  • 2014-03-15
  • 1970-01-01
  • 1970-01-01
  • 2016-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多