我有以下选择,在大型数据库上,它很慢:
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)
答案 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_size
和random_page_cost
似乎没有影响力。
(only 348 rows in track_event)
,这意味着seqscan将优于其他任何东西。该计划需要348行:使用索引无法获得选择性。 (主表需要从主表中获取event_id
值)
所以,我的猜测是,可以通过将work_mem和effective_cache_size设置为可用值来解决问题(在生产数据库上)。这当然只有在指数具有选择性时才有效;对于检索100%行的查询,索引几乎没用。