使用 PostgreSQL 优化搜索
您的搜索一开始就锚定,并且不需要模糊搜索逻辑。这不是全文搜索的典型用例。
如果它变得更加模糊或者您的搜索没有在一开始就锚定,请在此处查看更多信息:
在 PostgreSQL 中,您可以利用 高级索引功能,这会使查询非常快。特别注意operator classes 和indexes on expressions。
1) text_pattern_ops
假设您的列是文本类型,您可以为 文本模式运算符 使用特殊索引,如下所示:
CREATE INDEX name_text_pattern_ops_idx
ON tbl (name text_pattern_ops);
SELECT name
FROM tbl
WHERE name ~~ ('Hambu' || '%');
这是假设您使用 C 以外的数据库区域设置进行操作 - 在您的情况下很可能是 de_DE.UTF-8。您可以还使用区域设置“C”设置数据库。我引用manual here:
如果您使用 C 语言环境,则不需要 xxx_pattern_ops
运算符类,因为具有默认运算符类的索引是
可用于 C 语言环境中的模式匹配查询。
2) 表达式索引
我想您还希望使该搜索不区分大小写。所以让我们再迈出一步,让它成为表达式的索引:
CREATE INDEX lower_name_text_pattern_ops_idx
ON tbl (lower(name) text_pattern_ops);
SELECT name
FROM tbl
WHERE lower(name) ~~ (lower('Hambu') || '%');
要使用索引,WHERE 子句必须匹配索引表达式。
3) 优化索引大小和速度
最后,您可能还想对前导字符数施加限制,以最小化索引大小并进一步加快速度:
CREATE INDEX lower_left_name_text_pattern_ops_idx
ON tbl (lower(left(name,10)) text_pattern_ops);
SELECT name
FROM tbl
WHERE lower(left(name,10)) ~~ (lower('Hambu') || '%');
left() 是在 Postgres 9.1 中引入的。在旧版本中使用substring(name, 1,10)。
4) 覆盖所有可能的请求
超过 10 个字符的字符串呢?
SELECT name
FROM tbl
WHERE lower(left(name,10)) ~ (lower(left('Hambu678910',10)) || '%');
AND lower(name) ~~ (lower('Hambu678910') || '%');
这看起来是多余的,但您需要以这种方式拼写出来才能真正使用索引。索引搜索会将其缩小到几个条目,附加子句过滤其余条目。尝试找到最佳位置。取决于数据分布和典型用例。 10 个字符似乎是一个很好的起点。对于超过 10 个字符,left() 有效地变成了一种非常快速且简单的散列算法,足以满足许多(但不是全部)用例。
5) 使用CLUSTER 优化磁盘表示
因此,主要的访问模式将是根据我们的索引lower_left_name_text_pattern_ops_idx 检索一堆相邻的行。而你大部分时间阅读,几乎从不写作。这是CLUSTER的教科书案例。 The manual:
当一个表被聚集时,它会根据索引信息在物理上重新排序。
对于像您这样的大表,这可以显着缩短响应时间,因为要获取的所有行都位于磁盘上的相同或相邻块中。
第一次通话:
CLUSTER tbl USING lower_left_name_text_pattern_ops_idx;
将保存要使用的索引的信息,随后的调用将重新聚集表:
CLUSTER tbl;
CLUSTER; -- cluster all tables in the db that have previously been clustered.
如果不想重复:
ALTER TABLE tbl SET WITHOUT CLUSTER;
但是,CLUSTER 在表上使用排他锁。如果这是一个问题,请查看pg_repack 或pg_squeeze,它们可以在没有排他锁的情况下执行相同的操作。
6) 防止结果中出现过多行
要求搜索字符串至少包含 3 或 4 个字符。为了完整起见,我添加了这个,您可能还是会这样做。
和LIMIT返回的行数:
SELECT name
FROM tbl
WHERE lower(left(name,10)) ~~ (lower('Hambu') || '%')
LIMIT 501;
如果您的查询返回超过 500 行,请告诉用户缩小搜索范围。
7) 优化过滤方法(算子)
如果您绝对必须挤出最后的每一微秒,您可以使用text_pattern_ops family 的运算符。像这样:
SELECT name
FROM tbl
WHERE lower(left(name, 10)) ~>=~ lower('Hambu')
AND lower(left(name, 10)) ~<=~ (lower('Hambu') || chr(2097151));
你在最后一个特技中获得的收益很少。通常,标准运算符是更好的选择。
如果您这样做,搜索时间将减少到几毫秒。