慢选 - PostgreSQL

时间:2013-08-29 10:46:37

标签: sql postgresql

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

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 - 我想我想失去它,但是无法确定我可以使用哪个索引。

EDITS

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)

5 个答案:

答案 0 :(得分:1)

您的查询正在对较大的表进行全表扫描。显而易见的是在event_track(inboundid, eventid)上添加索引。 Postgres应该能够使用查询中的索引编写。您可以将查询重写为:

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

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

编辑:

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

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;

答案 1 :(得分:1)

尝试这种技巧:

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

答案 2 :(得分:1)

它取决于数据库中的记录数(特定表)。如果数据库很小,数据库的性质是静态的  并且非常罕见的记录增加,然后加入使用优于 IN 其中因为在加入后thay表现为表格而在小表格中加入需要微观秒。因为Where和 IN 具有特定的执行时间,如果数据库很大,那么在大型数据库中仍然会更好,如果数据库很小则可以获得快速结果,那么在使用In Statment 查询的情况下需要更多时间

适用于小型数据库

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);   

答案 3 :(得分:0)

可能是NULL没有编入索引,所以如果track_event.inboundid和temp_message.messageid可以为null,那么它就无法制作不涉及扫描的访问计划。

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

track_event表/索引有哪些统计信息?

答案 4 :(得分:0)

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

我可以通过禁用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似乎没有影响力。

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

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