【问题标题】:Finding similar strings with PostgreSQL quickly使用 PostgreSQL 快速查找相似字符串
【发布时间】:2012-06-30 06:25:46
【问题描述】:

我需要在表格中创建相似字符串的排名。

我有下表

create table names (
name character varying(255)
);

目前,我正在使用提供similarity 功能的pg_trgm 模块,但我遇到了效率问题。我创建了一个类似Postgres manual suggests 的索引:

CREATE INDEX trgm_idx ON names USING gist (name gist_trgm_ops);

我正在执行以下查询:

select (similarity(n1.name, n2.name)) as sim, n1.name, n2.name
from names n1, names n2
where n1.name != n2.name and similarity(n1.name, n2.name) > .8
order by sim desc;

查询有效,但是当您有数百个名字时,它真的很慢。此外,也许我忘记了一点 SQL,但我不明白为什么我不能使用条件 and sim > .8 而不会出现“列 sim 不存在”错误。

我想要任何提示来加快查询速度。

【问题讨论】:

    标签: sql postgresql text similarity postgresql-performance


    【解决方案1】:

    按照您的方式,必须计算表中每个元素与每个其他元素之间的相似性(几乎是交叉连接)。如果您的表有 1000 行,那已经是 1,000,000 (!) 个相似性计算,之前这些可以根据条件检查并排序。规模非常大。

    请改用SET pg_trgm.similarity_threshold% operator。两者都由pg_trgm 模块提供。这样,三元组 GiST 索引就可以发挥很大的作用。

    配置参数pg_trgm.similarity_threshold 替换了 Postgres 9.6 中的函数set_limit() and show_limit()。不推荐使用的函数仍然有效(从 Postgres 13 开始)。此外,自 Postgres 9.1 以来,GIN 和 GiST 索引的性能在许多方面都有所提高。

    试试吧:

    SET pg_trgm.similarity_threshold = 0.8;  -- Postgres 9.6 or later
      
    SELECT similarity(n1.name, n2.name) AS sim, n1.name, n2.name
    FROM   names n1
    JOIN   names n2 ON n1.name <> n2.name
                   AND n1.name % n2.name
    ORDER  BY sim DESC;
    

    快几个数量级,但仍然很慢。

    pg_trgm.similarity_threshold 是一个"customized" option,可以像任何其他选项一样处理。见:

    您可能希望通过添加前置条件(例如匹配首字母)交叉连接(并通过匹配的功能索引支持)来限制可能对的数量。 交叉连接的性能会随着 O(N²) 而下降。

    不起作用,因为您不能在WHEREHAVING 子句中引用输出列:

    WHERE ... sim > 0.8
    

    这是根据 SQL 标准(由某些其他 RDBMS 相当松散地处理)。另一方面:

    ORDER BY sim DESC
    

    有效,因为输出列可以GROUP BYORDER BY 中使用。见:

    测试用例

    我在旧的测试服务器上运行了一个快速测试来验证我的声明。
    PostgreSQL 9.1.4。使用EXPLAIN ANALYZE 的时间(最好的 5 次)。

    CREATE TEMP table t AS 
    SELECT some_col AS name FROM some_table LIMIT 1000;  -- real life test strings
    

    GIN指数第一轮测试:

    CREATE INDEX t_gin ON t USING gin(name gin_trgm_ops);  -- round1: with GIN index
    

    GIST索引第二轮测试:

    DROP INDEX t_gin;
    CREATE INDEX t_gist ON t USING gist(name gist_trgm_ops);
    

    新查询:

    SELECT set_limit(0.8);
    
    SELECT similarity(n1.name, n2.name) AS sim, n1.name, n2.name
    FROM   t n1
    JOIN   t n2 ON n1.name <> n2.name
               AND n1.name % n2.name
    ORDER  BY sim DESC;
    

    使用 GIN 索引,64 次点击:总运行时间:484.022 毫秒
    使用 GIST 索引,64 次点击:总运行时间:248.772 毫秒

    旧查询:

    SELECT (similarity(n1.name, n2.name)) as sim, n1.name, n2.name
    FROM   t n1, t n2
    WHERE  n1.name != n2.name
    AND    similarity(n1.name, n2.name) > 0.8
    ORDER  BY sim DESC;
    

    GIN 索引使用,64 次点击:总运行时间:6345.833 毫秒
    GIST 索引使用,64 次点击:总运行时间:6335.975 毫秒

    否则结果相同。建议很好。这仅适用于1000 行

    GIN 还是 GiST?

    GIN 通常提供卓越的读取性能:

    But not in this particular case!

    这可以通过 GiST 索引非常有效地实现,但不能通过 GIN 索引。

    【讨论】:

    • 精彩的回答,谢谢。你是对的,我可以在第一个字母的匹配上添加一个条件,但是在那些“名字”中我有名字和姓氏,有时写成“名字,姓氏”,有时写成“姓氏,名字”......我的附加问题与 order by 中别名的使用无关,而是与 where 条件有关。我认为可以为每一对计算一次相似度。
    • @cdarwin:啊,我把你的附属问题弄错了,抱歉。现已修改。信息仍然很好 - 特别是我提供的链接仍然适用。
    • 注意 set_limit() 现在已弃用,以代替 similarity_threshold GUC 变量。
    • 如何打印我当前的pg_trgm.similarity_threshold
    • @HMarioD: EXECUTE 'SET pg_trgm.similarity_threshold = ' || _threshold; 请参阅:stackoverflow.com/a/36025963/939860(当输入是数字类型时,这对 SQLi 是安全的。)
    猜你喜欢
    • 2019-12-27
    • 1970-01-01
    • 2015-08-18
    • 2020-12-09
    • 2023-03-15
    • 2021-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多