我有一个问题,希望得到你的回答: - )
所以,我拿了geonames.org并将所有德国城市的数据导入所有地区。
如果我进入“汉堡”,它会列出“汉堡中心,汉堡机场”等。该应用程序位于一个无法访问互联网的封闭网络中,因此我无法访问geonames.org Web服务并且必须导入数据。 :( 这个城市及其所有地区都是汽车完工。因此,每次击键都会导致XHR请求等等。
现在我的客户询问是否有可能拥有世界上的所有数据。最后,大约5.000.000行,有45.000.000个替代名称等。
Postgres每次查询需要大约3秒钟,这使得自动完成无法使用。
现在我想到了CouchDb,已经使用过了。我的问题:
我想发布“Ham”,我希望CouchDB获取所有以“Ham”开头的文档。如果我进入“汉堡”,我希望它返回汉堡等等。
CouchDB是否适合它?您可以推荐哪些其他数据库以低延迟(可能是内存中)和数百万个数据集进行响应?数据集不会定期更改,而是相当静态!
答案 0 :(得分:19)
如果我理解你的问题,可能你所需要的只是已经在CouchDB中构建。
startkey="Ham"&endkey="Ham\ufff0"
这是一个视图函数:
function(doc) {
for (var name in doc.places) {
emit(name, doc._id);
}
}
另请参阅有关CouchDB typeahead and autocomplete search的CouchOne博客文章以及有关CouchDB autocomplete的邮件列表中的此讨论。
答案 1 :(得分:15)
您的搜索锚定在开头,不需要模糊搜索逻辑。这是不全文搜索的典型用例。
如果它变得更模糊或者您的搜索不在开始时锚定,请查看此处了解更多信息:
Similar UTF-8 strings for autocomplete field
More on pattern matching in Postgres.
在PostgreSQL中,您可以使用高级索引功能,使查询非常快。请特别注意operator classes和indexes on expressions。
假设您的列是文本类型,您可以使用文本模式运算符的特殊索引,如下所示:
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语言环境中的模式匹配查询。
我想你也想让搜索不区分大小写。所以让我们采取另一个步骤,使其成为表达式的索引:
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
子句必须与索引表达式匹配。
最后,您可能还希望对主要字符数施加限制,以最大限度地减少索引的大小并进一步加快速度:
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') || '%');
Postgres 9.1引入了 left()
。在旧版本中使用substring(name, 1,10)
。
超过10个字符的字符串呢?
SELECT name
FROM tbl
WHERE lower(left(name,10)) ~ (lower(left('Hambu678910',10)) || '%');
AND lower(name) ~~ (lower('Hambu678910') || '%');
这看起来多余,但您需要拼写这种方式来实际使用索引。索引搜索会将其缩小到几个条目,附加条款会过滤其余条目。尝试找到最佳点。取决于数据分布和典型用例。 10个字符似乎是一个很好的起点。对于超过10个字符,left()
有效地变成了一种非常快速且简单的散列算法,这对于许多(但不是全部)用例来说已经足够了。
CLUSTER
因此,主要的访问模式是根据索引lower_left_name_text_pattern_ops_idx
检索一堆相邻的行。而你大部分都是读书而且几乎没有写过。这是CLUSTER
的教科书案例。我quote 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;
对于具有更多写入负载的表,请查看pg_repack
,这可以在没有表的独占锁的情况下执行相同操作。
要求搜索字符串至少包含3或4个字符。为了完整起见,我补充说,无论如何你可能会这样做
并LIMIT
返回的行数:
SELECT name
FROM tbl
WHERE lower(left(name,10)) ~~ (lower('Hambu') || '%')
LIMIT 501;
如果您的查询返回超过500行,请告诉用户缩小其搜索范围。
如果你绝对必须挤出每一微秒,你可以使用text_pattern_ops family的运算符。像这样:
SELECT name
FROM tbl
WHERE lower(left(name, 10)) ~>=~ lower('Hambu')
AND lower(left(name, 10)) ~<=~ (lower('Hambu') || chr(2097151));
你最后的特技表现得很少。通常,标准操作员是更好的选择。
答案 2 :(得分:10)
我认为更好的方法是将数据保存在数据库(Postgres或CouchDB)上,并使用全文搜索引擎(如Lucene,Solr或ElasticSearch)对其进行索引。