使用相似性 Postgres 模糊自连接查询提高性能

2024-02-14

我正在尝试运行一个查询,该查询将表与自身连接起来,并进行模糊字符串比较(使用三元组比较)以查找可能的公司名称匹配。我的目标是返回一个记录的公司名称(ref_name 字段)的三元相似度与另一记录的公司名称匹配的记录。目前,我将阈值设置为 0.9,因此它只会返回很可能包含相似字符串的匹配项。

我知道自连接本质上会导致许多比较,但我想尽我所能优化我的查询。我不需要立即得到结果,但目前我运行的查询需要 11 个小时才能运行。

我在 Ubuntu 12.04 服务器上运行 Postgres 9.2。我不知道 ref_name 字段(我匹配的字段)的最大长度是多少,所以我将其设置为varchar(300)。我想知道将其设置为文本类型是否会影响性能,或者是否有更好的字段类型可用于提高性能。我的LC_CTYPE and LC_COLLATE语言环境设置为"en_US.UTF-8"

我正在运行查询的表总共包含大约 160 万条记录,但需要我运行 11 个小时的查询是针对其中的一小部分(大约 100k)。

表结构:

CREATE TABLE ref_name (
  ref_name_id integer,
  ref_name character varying(300),
  ref_name_type character varying(2),
  name_display text,
  load_date timestamp without time zone
)

Indexes:

CREATE INDEX ref_name_ref_name_trigram_idx ON ref_name
  USING gist (ref_name COLLATE pg_catalog."default" gist_trgm_ops);

CREATE INDEX ref_name_ref_name_trigram_idx_1 ON ref_name
  USING gist (ref_name COLLATE pg_catalog."default" gist_trgm_ops)
  WHERE ref_name_type::text = 'E'::text;

CREATE INDEX ref_name_ref_name_e_idx ON ref_name
  USING btree (ref_name COLLATE pg_catalog."default")
  WHERE ref_name_type::text = 'E'::text;

Query:

select a.ref_name_id as name_id,a.ref_name AS name,
  a.name_display AS name_display,b.ref_name_id AS matched_name_id,
  b.ref_name AS matched_name,b.name_display AS matched_name_display
from ref_name a
JOIN ref_name b
 ON a.ref_name_id<>b.ref_name_id
 AND a.ref_name_id>b.ref_name_id
 AND a.ref_name % b.ref_name
WHERE 
 a.ref_name ~>=~ 'A' and a.ref_name ~<~'B'
 AND b.ref_name ~>=~ 'A' and b.ref_name ~<~'B'
 AND a.ref_name_type='E'
 AND b.ref_name_type='E'

解释计划:

"Nested Loop  (cost=0.00..8560728.16 rows=3598470 width=96)"
"  ->  Seq Scan on ref_name a  (cost=0.00..96556.12 rows=103901 width=48)"
"        Filter: (((ref_name)::text ~>=~ 'A'::text) AND ((ref_name)::text ~<~ 'B'::text) AND ((ref_name_type)::text = 'E'::text))"
"  ->  Index Scan using ref_name_ref_name_trigram_idx_1 on ref_name b  (cost=0.00..80.41 rows=35 width=48)"
"        Index Cond: ((a.ref_name)::text % (ref_name)::text)"
"        Filter: (((ref_name)::text ~>=~ 'A'::text) AND ((ref_name)::text ~<~ 'B'::text) AND (a.ref_name_id <> ref_name_id) AND (a.ref_name_id > ref_name_id))"

以下是一些示例记录:

1652632;"A 123 SYSTEMS";"E";"A 123 SYSTEMS INC";"2014-11-14 00:00:00"
1652633;"A123 SYSTEMS";"E";"A123 SYSTEMS INC";"2014-11-14 00:00:00"
1652640;"A 1 ACCOUSTICS";"E";"A-1 ACCOUSTICS";"2014-11-14 00:00:00"
1652641;"A 1 ACOUSTICS";"E";"A-1 ACOUSTICS";"2014-11-14 00:00:00"
1652642;"A1 ACOUSTICS";"E";"A1 ACOUSTICS INC";"2014-11-14 00:00:00"
1652650;"A 1 A ELECTRICAL";"E";"A-1 A ELECTRICAL INC";"2014-11-14 00:00:00"
1652651;"A 1 A ELECTRICIAN";"E";"A 1 A ELECTRICIAN INC";"2014-11-14 00:00:00"
1652652;"A 1A ELECTRICIAN";"E";"A 1A ELECTRICIAN INC";"2014-11-14 00:00:00"
1652653;"A1 A ELECTRICIAN";"E";"A1 A ELECTRICIAN INC";"2014-11-14 00:00:00"
1691270;"ALBERT GARLATTI";"E";"ALBERT GARLATTI";"2014-11-14 00:00:00"
1691271;"ALBERT GARLATTI CONSTRUCTION";"E";"ALBERT GARLATTI CONSTRUCTION CO";"2014-11-14 00:00:00"
1680892;"AG HOG PITTSBURGH";"E";"AG-HOG PITTSBURGH CO INC";"2014-11-14 00:00:00"
1680893;"AGHOG PITTSBURGH";"E";"AGHOG PITTSBURGH CO";"2014-11-14 00:00:00"
1680928;"AGILE PURSUITS FRACHISING";"E";"AGILE PURSUITS FRACHISING INC";"2014-11-14 00:00:00"
1680929;"AGILE PURSUITS FRANCHISING";"E";"AGILE PURSUITS FRANCHISING INC";"2014-11-14 00:00:00"
1680956;"AGING COMMUNITY COORDINATED ENTERPRISES & SUPPORT";"E";"AGING COMMUNITY COORDINATED ENTERPRISES & SUPPORT";"2014-11-14 00:00:00"
1680957;"AGING COMMUNITY COORDINATED ENTERPRISES & SUPPORTI";"E";"AGING COMMUNITY COORDINATED ENTERPRISES & SUPPORTI";"2014-11-14 00:00:00"

正如您所看到的,我创建了一个要点三元组索引来加快速度(到目前为止尝试了两种不同的类型进行比较)。有人对如何提高此查询的性能并将其从 11 小时缩短到更易于管理的时间有任何建议吗?最终我想在整个表上运行这个查询来比较记录,而不仅仅是这个小子集。


Indices

部分 GiST 索引很好,我至少会测试这两个额外的索引:

GIN 索引:

CREATE INDEX ref_name_trgm_gin_idx ON ref_name
USING gin (ref_name gin_trgm_ops)
WHERE ref_name_type = 'E';

这可能会或可能不会被使用。如果您升级到 Postgres 9.4,机会会好得多,因为 GIN 索引有了重大改进。

varchar_pattern_ops 索引:

CREATE INDEX ref_name_pattern_ops_idx
ON ref_name (ref_name varchar_pattern_ops)
WHERE ref_name_type = 'E';

Query

这个查询的核心问题是您遇到了交叉连接O(N²)当对照所有行检查所有行时。当行数非常大时,性能变得难以忍受。您似乎很了解动态。防御措施是限制可能的组合。您已经朝这个方向迈出了一步,限制为相同的第一个字母。

这里一个非常好的选择是建立在特殊才能的基础上GiST指数 for 最近的邻居搜索。有一个说明书上有提示 https://www.postgresql.org/docs/current/pgtrgm.html对于这种查询技术:

这可以通过 GiST 索引非常有效地实现,但不能通过 GIN 索引。当只有一个时,它通常会击败第一个配方 需要少量最接近的匹配。

A 杜松子酒指数可能仍然会习惯此外到 GiST 索引。你必须权衡成本和收益。在 9.4 之前的版本中,坚持使用一个大索引总体上可能会更便宜。但在 9.4 页中这可能是值得的。

Postgres 9.3+

Use a LATERAL加入匹配集到集。类似于章节2a在这个相关的答案中:

  • 优化 GROUP BY 查询以检索每个用户的最新行 https://stackoverflow.com/questions/25536422/optimize-group-by-query-to-retrieve-latest-record-per-user/25536748#25536748
SELECT a.ref_name_id
     , a.ref_name
     , a.name_display
     , b.ref_name_id  AS match_name_id
     , b.ref_name     AS match_name
     , b.name_display AS match_name_display
FROM   ref_name a
CROSS  JOIN LATERAL (
   SELECT b.ref_name_id, b.ref_name, b.name_display
   FROM   ref_name b
   WHERE  b.ref_name ~~ 'A%'
   AND    b.ref_name_type = 'E'
   AND    a.ref_name_id < b.ref_name_id
   AND    a.ref_name % b.ref_name  -- also enforce min. similarity
   ORDER  BY a.ref_name <-> b.ref_name
   LIMIT  10                                -- max. 10 best matches
   ) b
WHERE  a.ref_name ~~ 'A%'   -- you can extend the search
AND    a.ref_name_type = 'E'
ORDER  BY 1;

fiddle https://dbfiddle.uk/-j5lePwo - with all variants compared to your original query on 40k rows modeled after your case.
Old sqlfiddle http://sqlfiddle.com/#!17/5d4be/1

查询速度比小提琴中的原始查询快 2 - 5 倍。我希望他们能够规模更好有数百万行。你必须进行测试。

扩展对匹配项的搜索b到所有行(同时限制候选者a到一个合理的数字)也相当便宜。我在小提琴中添加了另外两个变体。

旁白:我运行了所有测试text代替varchar,但这应该没有什么区别。

基础知识和链接:

  • 使用 LIKE、SIMILAR TO 或正则表达式进行模式匹配 https://dba.stackexchange.com/a/10696/3684

Postgres 9.2

Use 相关子查询来替代尚未存在的缺失LATERAL join:

SELECT a.*
     , b.ref_name     AS match_name
     , b.name_display AS match_name_display
FROM  (
   SELECT ref_name_id
        , ref_name
        , name_display
        , (SELECT ref_name_id AS match_name_id
           FROM   ref_name b
           WHERE  ref_name_type = 'E'
           AND    ref_name ~~ 'A%'
           AND    ref_name_id > a.ref_name_id
           AND    ref_name % a.ref_name
           ORDER  BY ref_name <-> a.ref_name
           LIMIT  1                                -- max. 1 best match
          )
   FROM   ref_name a
   WHERE  ref_name ~~ 'A%'
   AND    ref_name_type = 'E'
   ) a
JOIN   ref_name b ON b.ref_name_id = a.match_name_id
ORDER  BY 1;

显然,这也需要一个索引ref_name_id,通常应该是 PK,因此会自动索引。

I added 还有两个变体 to the fiddle https://dbfiddle.uk/-j5lePwo.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用相似性 Postgres 模糊自连接查询提高性能 的相关文章

  • 在 plpgsql 函数中使用 quote_ident()

    我是创建 plpgsql 函数的新手 我需要一些有关在函数内部执行的动态命令上使用 quote ident 甚至 quote literal 的说明 希望有人能给我一个关于它们如何在函数内部工作的具体解释 TIA 这是一个例子 EXECUT
  • TypeScript 编译速度极慢 > 12 秒

    只是把它放在那里看看其他人是否也遇到这个问题 我已经使用 webpack 作为我的构建工具 使用 typescript 构建了一个 Angular 2 应用程序 一切都运行良好 但是我注意到 typescript 编译超级超级慢 我现在只有
  • 如何对 JSON 类型列进行分组/选择(PG::UndefinedFunction: 错误: 无法识别 json 类型的等式运算符)

    我想做
  • 快速像素绘图库

    我的应用程序以每像素的方式生成 动画 因此我需要有效地绘制它们 我尝试过不同的策略 库 但结果并不令人满意 尤其是在更高分辨率的情况下 这是我尝试过的 SDL 好的 但是慢 OpenGL 像素操作效率低下 xlib 更好 但仍然太慢 svg
  • 如何对单个 TypoSript 对象生成进行基准测试?

    我想对单个 TypoScript 对象生成进行基准测试以控制性能 是否可以使用某些 stdWrap 方法 我想要对其进行基准测试的 TS 对象示例 Test 1 page 10 RECORDS page 10 tables pages so
  • Swift - UICollectionView 重复(重复)单元格

    你好 我有一个数组 其中包含从 flickr 获取的 100 张图片 url 当我使用 UICollectionView 时 我显示 100 个单元格 屏幕上只有 8 个单元格 当我向下滚动查看下一个 8 个单元格时 它们与前一个单元格相同
  • NUMA 在虚拟内存中是如何表示的?

    有许多资源 https en wikipedia org wiki Non uniform memory access从硬件角度描述NUMA的架构性能影响 http practical tech com infrastructure num
  • 使用 psycopg2 转义 Postgres 的 SQL“LIKE”值

    psycopg2 是否有转义 a 值的函数LIKEPostgres 的操作数 例如 我可能想匹配以字符串 20 of all 开头的字符串 所以我想写这样的内容 sql WHERE LIKE myvalue s cursor fetchal
  • 性能:cakephp-mysql 中的 UUID 与自动递增

    我正在搜索 cakePHP 生成的 UUID 32 个字符长 是否比自动增量在性能上更快 插入和选择操作的比较 我应该使用 cakePHP 生成的 UUID 还是使用 MySQL 的简单自动增量生成的 UUID 这是我发现的一个案例研究 但
  • 授予用户 ALTER 函数的权限

    我试着ALTER一个新用户的函数 我收到错误 ERROR must be owner of function ACases Error ERROR must be owner of function ACases SQL state 425
  • MySQL通过UPDATE/DELETE合并重复数据记录

    我有一个看起来像这样的表 mysql gt SELECT FROM Colors ID USERNAME RED GREEN YELLOW BLUE ORANGE PURPLE 1 joe 1 null 1 null null null 2
  • 使用 Symfony 3 / Doctrine 进行属性形式的一对多对一

    问题是这样的 我有一个包含 3 个类的模型 person 人员 工作 job 一个人可以有多个工作 任何工作与人的关系都可以有 date start 属性 date end 和 comment 因此 我使用持有这些属性的可连接 person
  • 节省页面加载时间的提示[重复]

    这个问题在这里已经有答案了 我的问题 削减那些不必要的 kb 并使页面加载速度更快的最佳方法是什么 全部是什么优化实践 编码实践 在js php中 如果执行可以使您的页面更轻 为什么我问这个 我读了这篇关于 jquery js 与 jque
  • PostgreSQL 窗口函数:row_number() over(按 col2 分区 col 顺序)

    以下结果集源自具有一些连接和联合的 SQL 查询 SQL 查询已经对 Date 和 game 上的行进行了分组 我需要一列来描述按日期列分区的游戏的尝试次数 Username Game ID Date johndoe1 Game 1 100
  • 在 unix 中编译 dhrystone 时出错

    我是使用基准测试和 makefile 的新手 我已经从下面的链接下载了 Dhrystone 基准测试 我正在尝试编译它 但我遇到了奇怪的错误 我尝试解决它 但没有成功 有人可以帮助我运行 dhrystone 基准测试吗 以下是我尝试编译的两
  • Mono 实现 CLR 吗?或者至少有一些非托管的内部调用?或无?

    我们知道 C 使用非托管代码 如 P Invoke 或 CLR 实现的代码 如 InternalCall 我想知道的是 mono 它自己实现了一个完整的 CLR 还是只是一些非托管代码或者什么都没有 我可以使用 Net Reflactor或
  • 在生产代码/服务器上运行测试

    我在单元测试 自动化测试方面相对缺乏经验 所以如果这个问题没有任何意义 请原谅 我当前正在处理的代码库耦合如此紧密 以至于我需要重构大部分代码才能对其运行单元测试 所以我阅读了一些帖子并发现了 Selenium 我认为它确实是一个很酷的程序
  • 为什么反射会减慢Android手机的速度

    我多次读到反射会降低手机性能 这有多真实 例如 在我的例子中 我从 Web 服务获取一些参数 这些参数与我在 Android 应用程序中的类的参数同名 所以我只是使用java字段和反射设置这些参数的值 它似乎并没有降低性能 有人可以向我解释
  • PostgreSQL WHERE 计数条件

    我在 PostgreSQL 中有以下查询 SELECT COUNT a log id AS overall count FROM Log as a License as b WHERE a license id 7 AND a licens
  • LEFT JOIN 比 INNER JOIN 快得多

    我有一张桌子 MainTable 有超过 600 000 条记录 它通过第二个表连接到自身 JoinTable 在父 子类型关系中 SELECT Child ID Parent ID FROM MainTable AS Child JOIN

随机推荐