使用全文索引的全文搜索[实验性]
全文索引是一种实验性的二级索引类型,它为String或FixedString列提供了快速的文本搜索功能。全文索引的主要思想是存储从“词条”到包含这些词条的行之间的映射。“词条”是字符串列的标记化单元。例如,字符串单元“I will be a little late”默认情况下被标记化为六个词条“I”、“will”、“be”、“a”、“little”和“late”。另一种标记化器是 n-gram。例如,3-gram 标记化的结果将是 21 个词条“I w”、“ wi”、“wil”、“ill”、“ll ”、“l b”、“ be”等。输入字符串的标记化越细粒度,生成的全文索引就越大,但也越有用。
全文索引处于实验阶段,目前不应在生产环境中使用。它们将来可能会以向后不兼容的方式发生更改,例如,关于它们的 DDL/DQL 语法或性能/压缩特性。
用法
要使用全文索引,首先在配置中启用它们。
SET allow_experimental_full_text_index = true;
可以使用以下语法在字符串列上定义全文索引。
CREATE TABLE tab
(
`key` UInt64,
`str` String,
INDEX inv_idx(str) TYPE full_text(0) GRANULARITY 1
)
ENGINE = MergeTree
ORDER BY key
在 ClickHouse 的早期版本中,相应的索引类型名称为 inverted
。
其中 N
指定标记化器。
full_text(0)
(或简写为:full_text()
)将标记化器设置为“tokens”,即沿着空格拆分字符串,full_text(N)
,其中N
在 2 到 8 之间,将标记化器设置为“ngrams(N)”。
每个发布列表的最大行数可以作为第二个参数指定。此参数可用于控制发布列表的大小,以避免生成巨大的发布列表文件。存在以下变体。
full_text(ngrams, max_rows_per_postings_list)
:使用给定的 max_rows_per_postings_list(假设它不为 0)。full_text(ngrams, 0)
:对每个发布列表的最大行数没有限制。full_text(ngrams)
:使用默认最大行数,即 64K。
作为一种跳过索引,可以在创建表后删除或添加到列中。
ALTER TABLE tab DROP INDEX inv_idx;
ALTER TABLE tab ADD INDEX inv_idx(s) TYPE full_text(2);
要使用索引,不需要特殊的函数或语法。典型的字符串搜索谓词会自动利用索引。例如,请考虑
INSERT INTO tab(key, str) values (1, 'Hello World');
SELECT * from tab WHERE str == 'Hello World';
SELECT * from tab WHERE str IN ('Hello', 'World');
SELECT * from tab WHERE str LIKE '%Hello%';
SELECT * from tab WHERE multiSearchAny(str, ['Hello', 'World']);
SELECT * from tab WHERE hasToken(str, 'Hello');
全文索引也适用于 Array(String)
、Array(FixedString)
、Map(String)
和 Map(String)
类型的列。
与其他二级索引一样,每个列部分都有自己的全文索引。此外,每个全文索引在内部被划分为“段”。段的存在和大小通常对用户是透明的,但段大小决定了索引构建期间的内存消耗(例如,当两个部分合并时)。配置参数“max_digestion_size_per_segment”(默认值:256 MB)控制从底层列读取的数据量,然后创建新的段。增加此参数会增加索引构建的中间内存消耗,但也提高了查找性能,因为平均需要检查的段更少才能评估查询。
Hacker News 数据集的全文搜索
让我们看看全文索引在大型数据集(包含大量文本)上的性能改进。我们将使用 2870 万行热门 Hacker News 网站上的评论。以下是没有全文索引的表。
CREATE TABLE hackernews (
id UInt64,
deleted UInt8,
type String,
author String,
timestamp DateTime,
comment String,
dead UInt8,
parent UInt64,
poll UInt64,
children Array(UInt32),
url String,
score UInt32,
title String,
parts Array(UInt32),
descendants UInt32
)
ENGINE = MergeTree
ORDER BY (type, author);
2870 万行数据位于 S3 中的一个 Parquet 文件中——让我们将它们插入到 hackernews
表中。
INSERT INTO hackernews
SELECT * FROM s3Cluster(
'default',
'https://datasets-documentation.s3.eu-west-3.amazonaws.com/hackernews/hacknernews.parquet',
'Parquet',
'
id UInt64,
deleted UInt8,
type String,
by String,
time DateTime,
text String,
dead UInt8,
parent UInt64,
poll UInt64,
kids Array(UInt32),
url String,
score UInt32,
title String,
parts Array(UInt32),
descendants UInt32');
考虑以下在 comment
列中搜索词条 ClickHouse
(及其各种大小写)的简单搜索。
SELECT count()
FROM hackernews
WHERE hasToken(lower(comment), 'clickhouse');
请注意,执行查询需要 3 秒。
┌─count()─┐
│ 1145 │
└─────────┘
1 row in set. Elapsed: 3.001 sec. Processed 28.74 million rows, 9.75 GB (9.58 million rows/s., 3.25 GB/s.)
我们将使用 ALTER TABLE
并添加一个在 comment
列的小写形式上的全文索引,然后将其物化(这可能需要一段时间——等待它物化)。
ALTER TABLE hackernews
ADD INDEX comment_lowercase(lower(comment)) TYPE full_text;
ALTER TABLE hackernews MATERIALIZE INDEX comment_lowercase;
我们运行相同的查询……
SELECT count()
FROM hackernews
WHERE hasToken(lower(comment), 'clickhouse')
……并注意到查询执行速度提高了 4 倍。
┌─count()─┐
│ 1145 │
└─────────┘
1 row in set. Elapsed: 0.747 sec. Processed 4.49 million rows, 1.77 GB (6.01 million rows/s., 2.37 GB/s.)
我们还可以搜索一个或多个词条中的一个或全部,即析取或合取。
-- multiple OR'ed terms
SELECT count(*)
FROM hackernews
WHERE multiSearchAny(lower(comment), ['oltp', 'olap']);
-- multiple AND'ed terms
SELECT count(*)
FROM hackernews
WHERE hasToken(lower(comment), 'avx') AND hasToken(lower(comment), 'sve');
与其他二级索引不同,全文索引(目前)映射到行号(行 ID)而不是粒度 ID。这种设计的理由是性能。在实践中,用户通常会同时搜索多个词条。例如,过滤器谓词 WHERE s LIKE '%little%' OR s LIKE '%big%'
可以通过形成词条“little”和“big”的行 ID 列表的并集,使用全文索引直接进行评估。这也意味着提供给索引创建的参数 GRANULARITY
没有任何意义(将来可能会从语法中删除)。