哪个记录导致通知:单词太长而无法建立索引

lin*_*jon 4 postgresql ruby-on-rails

在 Rails 应用程序中使用 Postgres(使用 pg_search gem),我启用了 tsvector 搜索。在一个拥有超过 35,000 条记录的数据库中,我收到几条消息说

NOTICE:  word is too long to be indexed
DETAIL:  Words longer than 2047 characters are ignored.
Run Code Online (Sandbox Code Playgroud)

我假设“单词”不包含空格是否正确?我如何确定哪些记录导致此消息?

这是迁移生成的 SQL,其中引入了索引

 ==  AddIndexForFullTextSearch: migrating ======================================
-- add_column(:posts, :tsv, :tsvector)
   -> 0.0344s
-- execute("      CREATE INDEX index_posts_tsv ON posts USING gin(tsv);\n")
   -> 0.1694s
-- execute("    UPDATE posts SET tsv = (to_tsvector('english', coalesce(title, '')) || \n                            to_tsvector('english', coalesce(intro, '')) || \n                            to_tsvector('english', coalesce(body, '')));\n")
NOTICE:  word is too long to be indexed
DETAIL:  Words longer than 2047 characters are ignored.
NOTICE:  word is too long to be indexed
DETAIL:  Words longer than 2047 characters are ignored.
NOTICE:  word is too long to be indexed
DETAIL:  Words longer than 2047 characters are ignored.
NOTICE:  word is too long to be indexed
DETAIL:  Words longer than 2047 characters are ignored.
   -> 343.0556s
-- execute("      CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE\n      ON posts FOR EACH ROW EXECUTE PROCEDURE\n      tsvector_update_trigger(tsv, 'pg_catalog.english', title, intro, body);\n")
   -> 0.0266s
Run Code Online (Sandbox Code Playgroud)

max*_*zig 5

Postgres 全文搜索限制最大​​标记长度为 2047 字节:

\n\n
\n

每个lexeme的长度必须小于2K字节

\n
\n\n

如果使用的文本解析器返回更长的标记,那么您会收到“通知”消息,就像您在问题中引用的那样,例如:

\n\n
select to_tsvector(\'english\', repeat(\'x\', 2*1024));\nNOTICE:  word is too long to be indexed\nDETAIL:  Words longer than 2047 characters are ignored.\n
Run Code Online (Sandbox Code Playgroud)\n\n

在这种情况下,“word”实际上意味着标记,默认的 Postgres 文本解析器可能会返回达到此限制的空白标记。

\n\n

您可以使用仅调用文本解析器并选择所有过长标记的查询来识别产生此通知的记录。例如:

\n\n
select t.id, tt.tokid, tt.alias, length(t.token), t.token from (\n    select  id, (ts_parse(\'default\', payload)).* from content) t\n    inner join ts_token_type(\'default\') tt\n       on t.tokid = tt.tokid\n    where length(token) >= 2*1024;\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果解析器产生长标记的原因不明显,可以查看示例记录中的前/后标记 - 如下所示:

\n\n
select case when length(token)>128 then \'###\' else \'\' end,\n       t.tokid, tt.alias, length(token), token\n       from ts_parse(\'default\',\n                     (select payload from content where id = \'foobar\')) t\n    inner join ts_token_type(\'default\') tt\n        on t.tokid = tt.tokid;\n
Run Code Online (Sandbox Code Playgroud)\n\n

您可以搜索###并查看上下文标记以了解解析是如何出错的。

\n\n

例如,默认的 Postgres 文本解析器还返回 HTML/XML 样式标签,并将它们作为单独的标签标记返回。从 Postgres 11 开始,如果它只看到一个开始“标签”而没有后面的结束标签,它有时会返回一些以下文本作为人工空白标记。例如:

\n\n
select case when length(token)>128 then \'###\' else \'\' end, t.tokid, tt.alias,\n    length(token), token from ts_parse(\'default\', (select $$<script> \n\n           We should forget about small efficiencies, say about 97% of the time\n\n       <script>\n\n           premature optimization is the root of all evil.\n\n$$)) t inner join ts_token_type(\'default\') tt on t.tokid = tt.tokid;\n
Run Code Online (Sandbox Code Playgroud)\n\n

它被解析为 4 个标记,其中空白标记甚至包含一些文本:

\n\n
case tokid alias length\n\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80 \xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80 \xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80 \xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n        13 tag        8\n        12 blank     90                           \n        13 tag        8\n        12 blank     62\n(4 rows)\n
Run Code Online (Sandbox Code Playgroud)\n\n

(为简洁起见,省略最后一列)

\n\n

如果此类伪“标签”之间存在真实段落,则此类伪“空白/空白”标记很容易达到 2k 限制。

\n\n

解决这个问题的快速方法是替换文本参数<>中的字符to_tsvector()/ts_parse(),这样默认的 Postgres 解析器就不会将<>包含的单词错误地识别为标签,例如:

\n\n
... regexp_replace($$...$$, \'[<>]\', \'\', \'g\') ...\n
Run Code Online (Sandbox Code Playgroud)\n\n

不幸的是,默认 Postgres 文本解析器的功能(例如标签检测)不可参数化(从版本 11 开始)。可以使用自定义解析器,但要创建自定义解析器目前基本上意味着用 C 语言编写一个新的解析器并将其作为额外扩展加载 - 这可以说是乏味且容易出错的。

\n