Jam*_*nah 10 postgresql performance full-text-search execution-plan postgresql-9.1 query-performance
针对此数据库的全文查询(存储 RT(请求跟踪器)票证)似乎需要很长时间才能执行。附件表(包含全文数据)大约为 15GB。
数据库模式如下,大约有 200 万行:
rt4=# \d+ 附件 表“public.attachments” 专栏 | 类型 | 修饰符 | 存储 | 描述 -----------------+------------------------------------------+-- -------------------------------------------------- -------+----------+------------- 身份证 | 整数 | not null default nextval('attachments_id_seq'::regclass) | 平原 | 交易ID | 整数 | 不为空| 平原 | 家长 | 整数 | 非空默认值 0 | 平原 | 消息ID | 字符变化(160) | | 扩展 | 主题 | 字符变化(255) | | 扩展 | 文件名 | 字符变化(255) | | 扩展 | 内容类型 | 字符变化(80) | | 扩展 | 内容编码 | 字符变化(80) | | 扩展 | 内容 | 文字 | | 扩展 | 标题| 文字 | | 扩展 | 创造者 | 整数 | 非空默认值 0 | 平原 | 创建 | 没有时区的时间戳 | | 平原 | 内容索引 | 向量 | | 扩展 | 索引: “attachments_pkey”主键,btree (id) “attachments1”btree(父) "attachments2" btree (transactionid) “attachments3”btree(父,transactionid) “contentindex_idx”杜松子酒(内容索引) 有 OID:否
我可以使用以下查询非常快速(<1s)查询自己的数据库:
select objectid
from attachments
join transactions on attachments.transactionid = transactions.id
where contentindex @@ to_tsquery('frobnicate');
Run Code Online (Sandbox Code Playgroud)
但是,当 RT 运行一个应该对同一个表执行全文索引搜索的查询时,通常需要数百秒才能完成。查询分析输出如下:
SELECT COUNT(DISTINCT main.id)
FROM Tickets main
JOIN Transactions Transactions_1 ON ( Transactions_1.ObjectType = 'RT::Ticket' )
AND ( Transactions_1.ObjectId = main.id )
JOIN Attachments Attachments_2 ON ( Attachments_2.TransactionId = Transactions_1.id )
WHERE (main.Status != 'deleted')
AND ( ( ( Attachments_2.ContentIndex @@ plainto_tsquery('frobnicate') ) ) )
AND (main.Type = 'ticket')
AND (main.EffectiveId = main.id);
Run Code Online (Sandbox Code Playgroud)
EXPLAIN ANALYZE
输出查询计划 -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------- 合计(cost=51210.60..51210.61 rows=1 width=4)(实际时间=477778.806..477778.806 rows=1 loops=1) -> Nested Loop (cost=0.00..51210.57 rows=15 width=4) (实际时间=17943.986..477775.174 rows=4197 loops=1) -> Nested Loop (cost=0.00..40643.08 rows=6507 width=8) (实际时间=8.526..20610.380 rows=1714818 loops=1) -> Seq Scan on ticket main (cost=0.00..9818.37 rows=598 width=8) (实际时间=0.008..256.042 rows=96990 loops=1) 过滤器:(((status)::text 'deleted'::text) AND (id = Effectiveid) AND ((type)::text = 'ticket'::text)) -> 使用transactions1 对事务transactions_1 进行索引扫描(成本=0.00..51.36 行=15 宽度=8)(实际时间=0.102..0.202 行=18 次循环=96990) 索引条件:(((objecttype)::text = 'RT::Ticket'::text) AND (objectid = main.id)) -> 在附件attachments_2上使用attachments2进行索引扫描(成本=0.00..1.61行=1宽度=4)(实际时间=0.266..0.266行=0循环=1714818) 索引条件:(transactionid = transactions_1.id) 过滤器:(contentindex @@plainto_tsquery('frobnicate'::text)) 总运行时间:477778.883 毫秒
据我所知,问题似乎是它没有使用在contentindex
字段 ( contentindex_idx
)上创建的索引,而是对附件表中的大量匹配行进行过滤。解释输出中的行数似乎也非常不准确,即使在最近的ANALYZE
:估计行数=6507 实际行数=1714818 之后。
我不太确定接下来要做什么。
这可以通过一千零一种方式改进,那么它应该是几毫秒的事情。
这只是您使用别名重新格式化的查询,并删除了一些噪音以清除迷雾:
SELECT count(DISTINCT t.id)
FROM tickets t
JOIN transactions tr ON tr.objectid = t.id
JOIN attachments a ON a.transactionid = tr.id
WHERE t.status <> 'deleted'
AND t.type = 'ticket'
AND t.effectiveid = t.id
AND tr.objecttype = 'RT::Ticket'
AND a.contentindex @@ plainto_tsquery('frobnicate');
Run Code Online (Sandbox Code Playgroud)
您的查询的大部分问题在于前两个表tickets
和transactions
,它们在问题中缺失。我正在填写有根据的猜测。
t.status
,t.objecttype
并且tr.objecttype
可能不应该是text
,但enum
也可能是引用查找表的一些非常小的值。EXISTS
半连接假设tickets.id
是主键,这种重写的形式应该便宜得多:
SELECT count(*)
FROM tickets t
WHERE status <> 'deleted'
AND type = 'ticket'
AND effectiveid = id
AND EXISTS (
SELECT 1
FROM transactions tr
JOIN attachments a ON a.transactionid = tr.id
WHERE tr.objectid = t.id
AND tr.objecttype = 'RT::Ticket'
AND a.contentindex @@ plainto_tsquery('frobnicate')
);
Run Code Online (Sandbox Code Playgroud)
不是将行与两个 1:n 连接相乘,只是在最后折叠多个匹配项count(DISTINCT id)
,而使用EXISTS
半连接,一旦找到第一个匹配项,它就可以停止进一步查找,同时废弃最后DISTINCT
一步。根据文档:
子查询通常只会执行足够长的时间来确定是否至少返回一行,而不是一直执行到完成。
有效性取决于每张票和每笔交易的附件有多少交易。
join_collapse_limit
如果您知道您的搜索词 forattachments.contentindex
是非常有选择性的- 比查询中的其他条件更具选择性('frobnicate' 可能是这种情况,但不是 'problem' 的情况),您可以强制连接序列。查询规划器很难判断特定词的选择性,除了最常见的词。根据文档:
join_collapse_limit
(integer
)[...]
因为查询规划器并不总是选择最佳连接顺序,高级用户可以选择暂时将此变量设置为 1,然后明确指定他们想要的连接顺序。
使用SET LOCAL
为宗旨,以仅设置为当前事务。
BEGIN;
SET LOCAL join_collapse_limit = 1;
SELECT count(DISTINCT t.id)
FROM attachments a -- 1st
JOIN transactions tr ON tr.id = a.transactionid -- 2nd
JOIN tickets t ON t.id = tr.objectid -- 3rd
WHERE t.status <> 'deleted'
AND t.type = 'ticket'
AND t.effectiveid = t.id
AND tr.objecttype = 'RT::Ticket'
AND a.contentindex @@ plainto_tsquery('frobnicate');
ROLLBACK; -- or COMMIT;
Run Code Online (Sandbox Code Playgroud)
WHERE
条件的顺序总是无关紧要的。这里只有连接的顺序是相关的。
或者使用像@jjanes 在“选项 2”中解释的 CTE 。为了类似的效果。
采取的所有条件tickets
与其中的大多数查询使用方法相同,并创建一个部分索引上tickets
:
CREATE INDEX tickets_partial_idx
ON tickets(id)
WHERE status <> 'deleted'
AND type = 'ticket'
AND effectiveid = id;
Run Code Online (Sandbox Code Playgroud)
如果其中一个条件是可变的,请将其从WHERE
条件中删除,并将该列作为索引列添加到前面。
另一个关于transactions
:
CREATE INDEX transactions_partial_idx
ON transactions(objecttype, objectid, id)
Run Code Online (Sandbox Code Playgroud)
第三列只是启用仅索引扫描。
此外,由于您有这个带有两个整数列的复合索引attachments
:
"attachments3" btree (parent, transactionid)
Run Code Online (Sandbox Code Playgroud)
这个额外的索引完全是浪费,删除它:
"attachments1" btree (parent)
Run Code Online (Sandbox Code Playgroud)
细节:
添加transactionid
到您的 GIN 索引以使其更有效。这可能是另一个灵丹妙药,因为它可能允许仅索引扫描,完全消除对大表的访问。
您需要由附加模块提供的附加运算符类btree_gin
。详细说明:
"contentindex_idx" gin (transactionid, contentindex)
Run Code Online (Sandbox Code Playgroud)
integer
列中的4 个字节不会使索引变大。另外,幸运的是,GIN 索引在一个关键方面与 B 树索引不同。根据文档:
多列 GIN 索引可以与涉及索引列的任何子集的查询条件一起使用 。与 B-tree 或 GiST 不同,无论查询条件使用哪个索引列,索引搜索的有效性都是相同的。
大胆强调我的。所以你只需要一个(大而且有点贵)GIN 索引。
移动integer not null columns
到前面。这对存储和性能有一些小的积极影响。在这种情况下,每行可节省 4 - 8 个字节。
Table "public.attachments"
Column | Type | Modifiers
-----------------+-----------------------------+------------------------------
id | integer | not null default nextval('...
transactionid | integer | not null
parent | integer | not null default 0
creator | integer | not null default 0 -- !
created | timestamp | -- !
messageid | character varying(160) |
subject | character varying(255) |
filename | character varying(255) |
contenttype | character varying(80) |
contentencoding | character varying(80) |
content | text |
headers | text |
contentindex | tsvector |
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
1582 次 |
最近记录: |