【问题标题】:Slow select - PostgreSQL慢速选择 - PostgreSQL
【发布时间】:2013-08-29 10:46:36
【问题描述】:

我有以下选择,在大型数据库上,它很慢:

SELECT eventid 
FROM track_event 
WHERE inboundid IN (SELECT messageid FROM temp_message);

temp_message 表很小(100 行),只有一列(messageid varchar),列上有一个 btree 索引。

track_event 表有 19 列和近 1300 万行。此查询中使用的列(eventid bigint 和 inboundid varchar)都有 btree 索引。

我无法从大数据库复制/粘贴解释计划,但这是来自具有相同架构的较小数据库(track_event 中只有 348 行)的计划:

 explain analyse SELECT eventid FROM track_event WHERE inboundid IN (SELECT messageid FROM temp_message);
                                                           QUERY PLAN                                                               
----------------------------------------------------------------------------------------------------------------------------------------
 Nested Loop Semi Join  (cost=0.00..60.78 rows=348 width=8) (actual time=0.033..3.186 rows=348 loops=1)
->  Seq Scan on track_event  (cost=0.00..8.48 rows=348 width=25) (actual time=0.012..0.860 rows=348 loops=1)
->  Index Scan using temp_message_idx on temp_message  (cost=0.00..0.48 rows=7 width=32) (actual time=0.005..0.005 rows=1 loops=348)
      Index Cond: ((temp_message.messageid)::text = (track_event.inboundid)::text)
Total runtime: 3.349 ms
(5 rows)

在大型数据库上,此查询大约需要 450 秒。任何人都可以看到任何明显的加速吗?我注意到解释计划中的 track_event 上有一个 Seq Scan - 我想我想失去它,但无法确定我可以使用哪个索引。

编辑

Postgres 9.0

track_event 表是一个非常大的复杂架构的一部分,我无法对其进行重大更改。这是信息,包括我刚刚添加的新索引:

            Table "public.track_event"
       Column       |           Type           | Modifiers 
--------------------+--------------------------+-----------
 eventid            | bigint                   | not null
 messageid          | character varying        | not null
 inboundid          | character varying        | not null
 newid              | character varying        | 
 parenteventid      | bigint                   | 
 pmmuser            | bigint                   | 
 eventdate          | timestamp with time zone | not null
 routeid            | integer                  | 
 eventtypeid        | integer                  | not null
 adminid            | integer                  | 
 hostid             | integer                  | 
 reason             | character varying        | 
 expiry             | integer                  | 
 encryptionendpoint | character varying        | 
 encryptionerror    | character varying        | 
 encryptiontype     | character varying        | 
 tlsused            | integer                  | 
 tlsrequested       | integer                  | 
 encryptionportal   | integer                  | 
Indexes:
    "track_event_pk" PRIMARY KEY, btree (eventid)
    "foo" btree (inboundid, eventid)
    "px_event_inboundid" btree (inboundid)
    "track_event_idx" btree (messageid, eventtypeid)
Foreign-key constraints:
    "track_event_parent_fk" FOREIGN KEY (parenteventid) REFERENCES track_event(eventid)
    "track_event_pmi_route_fk" FOREIGN KEY (routeid) REFERENCES pmi_route(routeid)
    "track_event_pmim_smtpaddress_fk" FOREIGN KEY (pmmuser) REFERENCES pmim_smtpaddress(smtpaddressid)
    "track_event_track_adminuser_fk" FOREIGN KEY (adminid) REFERENCES track_adminuser(adminid)
    "track_event_track_encryptionportal_fk" FOREIGN KEY (encryptionportal) REFERENCES track_encryptionportal(id)
    "track_event_track_eventtype_fk" FOREIGN KEY (eventtypeid) REFERENCES track_eventtype(eventtypeid)
    "track_event_track_host_fk" FOREIGN KEY (hostid) REFERENCES track_host(hostid)
    "track_event_track_message_fk" FOREIGN KEY (inboundid) REFERENCES track_message(messageid)
Referenced by:
    TABLE "track_event" CONSTRAINT "track_event_parent_fk" FOREIGN KEY (parenteventid) REFERENCES track_event(eventid)
    TABLE "track_eventaddress" CONSTRAINT "track_eventaddress_track_event_fk" FOREIGN KEY (eventid) REFERENCES track_event(eventid)
    TABLE "track_eventattachment" CONSTRAINT "track_eventattachment_track_event_fk" FOREIGN KEY (eventid) REFERENCES track_event(eventid)
    TABLE "track_eventrule" CONSTRAINT "track_eventrule_track_event_fk" FOREIGN KEY (eventid) REFERENCES track_event(eventid)
    TABLE "track_eventthreatdescription" CONSTRAINT "track_eventthreatdescription_track_event_fk" FOREIGN KEY (eventid) REFERENCES track_event(eventid)
    TABLE "track_eventthreattype" CONSTRAINT "track_eventthreattype_track_event_fk" FOREIGN KEY (eventid) REFERENCES track_event(eventid)
    TABLE "track_quarantineevent" CONSTRAINT "track_quarantineevent_track_event_fk" FOREIGN KEY (eventid) REFERENCES track_event(eventid)

【问题讨论】:

  • 1) mesaage_id 是否有任何理由成为 varchar 类型? 2)也许添加一个代理主键,将其用作FK,并在文本列上添加一个唯一约束? 3) 也许更喜欢EXISTS(...) to IN (...)`? (虽然计划已经显示了索引扫描)0)请在问题中添加表定义。 0a) 和可调参数 (random_page_cost, work_mem, ?)
  • 顺便说一句:索引条件上的演员 ((temp_message.messageid)::text = (track_event.inboundid)::text) 非常可疑我无法在这里重现它(pg9.3beta:我只得到 hashjoins;即使对于 varchar 键)。 Postgres 版本? 表定义 ?
  • 正如 joop 所说,添加您的 Postgres 版本。没有计划和版本就不能给出答案。
  • 解决了!一些答案非常有用,尽管没有一个是一针见血的。我遇到了问题,因为测试数据库非常小,因此两个数据库之间的查询行为不同,导致了一些混乱。最后,在 track_event 表上运行分析让它使用索引,几分钟的查询下降到毫秒。
  • 下次请发布真实查询。使用真实的模式和真实的数据。和真正的调音。这将为一些真实的人节省很多猜测。谢谢。

标签: sql postgresql


【解决方案1】:

您的查询正在对较大的表进行全表扫描。一个明显的加速是在event_track(inboundid, eventid) 上添加一个索引。 Postgres 应该能够按照所写的方式使用查询中的索引。您可以将查询重写为:

SELECT te.eventid
FROM track_event te join
     temp_message tm
     on te.inboundid  = tm.messageid;

绝对应该使用索引。 (如果temp_message 表中有重复项,您可能需要select distinct te.eventid。)

编辑:

最后一次尝试重写是反转查询:

select (select eventid from track_event te WHERE tm.messageid = te.inboundid) as eventid
from temp_message tm;

这应该强制使用索引。如果有不匹配的情况,您可能需要:

select eventid
from (select (select eventid from track_event te WHERE tm.messageid = te.inboundid) as eventid
      from temp_message tm
     ) tm
where eventid is not null;

【讨论】:

  • track_event 上仍有 Seq Scan
  • 可能是统计信息错误或不存在。或者预期的行数低于 10% (IIRC) 或者 DBMS 调整常量错误。
  • 查询计划可能会根据表的大小而有所不同。你应该在一张大桌子上测试它。
  • 尝试了修改后的语句。它窒息是因为嵌套选择返回多个结果:错误:用作表达式的子查询返回的不止一行
  • 嵌套查询应该使用EXISTS (...)
【解决方案2】:

试试这个技巧:

SELECT eventid FROM track_event te WHERE inboundid IN (SELECT messageid FROM temp_message where messageid = te.inboundid);   

或者您也可以使用以下代码以获得更好的结果

Select eventid From track_event te Where (Select count(*) from temp_message where messageid = te.inboundid) > 0

【讨论】:

    【解决方案3】:

    这取决于数据库中的记录数(特定表)。如果有小型数据库并且数据库的性质是静态的 并且非常罕见的记录增加,然后 join 使用比 INwhere 因为加入后表现得像一个表,在小表中加入需要微秒。因为 Where 和 IN 有特定的执行时间,如果数据库很大,那么在大型数据库中会更好,如果数据库很小,那么在使用 In Statment Query 的情况下会得到快速的结果需要更多时间

    对于小型数据库

    SELECT t1.column_name,t1.column_name,t2.column_name,t2.column_name FROM tbl1 t1
    INNER JOIN tbl2 t2
    ON tbl1.column_name=tbl2.column_name;
    

    对于大型数据库

    SELECT column_name,column_name FROM tbl1 t1 WHERE tbl1.column_name IN (SELECT column_name FROM tbl2 t2 where t2.column_name = t1.column_name);   
    

    【讨论】:

      【解决方案4】:

      可能是 NULL 未编入索引,因此如果 track_event.inboundid 和 temp_message.messageid 可以为 null,则它无法制定不涉及扫描的访问计划。

      即使有索引,如果没有选择性,也不能保证使用它是最好的计划。

      track_event 表/索引的统计数据是什么?

      【讨论】:

        【解决方案5】:

        问题可能是(错误)调整的结果。

        我可以通过禁用 hashjoin 和排序来重现该行为。对于足够大的查询,可能会在更大的输入数据集上调用相同的行为,一旦达到work_mem

        以下设置在此处重现 OP 的查询计划(PG9.3beta)

        -- SET work_mem = 64 ;
        -- SET enable_material = 0; -- only needed for pg9.3 ?
        SET enable_hashjoin = 0 ;
        SET enable_sort = 0 ;
        

        effective_cache_sizerandom_page_cost 似乎没有影响(目前)。

        顺便说一句:问题是:(only 348 rows in track_event),这意味着 seqscan 将优于其他任何东西。该计划需要 348 行:使用索引无法获得选择性。 (需要主表从主表中获取event_id值)

        所以,我的猜测是(在生产数据库上)问题可能可以通过将 work_mem 和 Effective_cache_size 设置为可用值来解决。这当然只有在索引是选择性的情况下才有效;对于检索 100% 行的查询,索引几乎没有用处。

        【讨论】:

          猜你喜欢
          • 2019-11-24
          • 2018-06-06
          • 2012-03-30
          • 1970-01-01
          • 2015-12-18
          • 1970-01-01
          • 2017-04-20
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多