【问题标题】:Reverse index for multiple keywords search多个关键字搜索的反向索引
【发布时间】:2013-11-25 14:54:23
【问题描述】:

我正在寻找以下问题的架构解决方案:

问题的一般描述

我有很多不同的数据实体(大约 1500 万个)。每个实体都与某些关键字(或标签)相关联(在最坏的情况下,每个实体从几个到数百个)。

给定N 不同的关键字,我的任务是按以下顺序检索以下结果:

  • 与所有N给定关键字相关联的所有实体;
  • 包含N-1给定关键字任意组合的实体;
  • 包含N-2 给定关键字的任意组合的实体;
  • 等等(可能只是到某些N-K-limit,但在一般情况下,到1-keyword匹配)。

天真的方法

我想到的天真的解决方案是使用 MySQL/PostgreSQL RDBMS 为每个关键字创建简单的反向索引。通常它将包含两个表:

Table Keywords              Table Entities
---------------------       ---------------------
   id      keyword            id     keyword_id
---------------------       ---------------------
    1         tag1             1              1
    2         tag2             1              2             
    3         tag3             2              3 
  • Keywords存储关键字;
  • Entities 用于存储实体id-s 和keyword_id-s 之间的关系。

对于每个 keyword1 & keyword2 & ... & keywordN 查询,我将检索每个查询关键字的所有实体 ID 集,然后在应用程序级别手动搜索 N-keywords、N-1-keywords 等数学。

问题

显然这种方法会遇到两个问题:

  1. 从十亿条目Entities 表中接收数据集的时间很长(即使使用索引);
  2. 长时间执行应用程序级别的搜索以在应用程序级别搜索N-keywords 匹配项。

对于这两个问题,请考虑一个标签可以与 数百万 一般情况下的条目相关联。

如何有效地处理这些问题?

【问题讨论】:

  • 转发dba.stackexchange.com/q/53877/7788 。请不要在站点之间复制和粘贴问题。浪费大家的时间。
  • @CraigRinger 我明白了,现在交叉帖被删除了。

标签: mysql database performance postgresql optimization


【解决方案1】:

我会为此使用 the intarray extension 和 GiST 索引。

用标签数组存储你的实体,例如:

CREATE EXTENSION intarray;

CREATE TABLE entity(
    entity_id BIGSERIAL PRIMARY KEY,
    tags integer[] not null
);

INSERT INTO entity(tags) values (ARRAY[1,2,3]);
INSERT INTO entity(tags) values (ARRAY[1,3,5]);
INSERT INTO entity(tags) values (ARRAY[1]);
INSERT INTO entity(tags) values (ARRAY[]::integer[]);

CREATE INDEX entity_tags_idx ON entity USING GIST(tags);

并用模糊的东西查询:

SELECT
    *,
    ARRAY[1,3] & tags AS matched_tags 
FROM entity 
WHERE ARRAY[1,3] && tags 
ORDER BY array_length(ARRAY[1,3] & tags,1) DESC;

索引将用于排除没有任何匹配标签的行。然后,结果集将按匹配标签的数量降序排序。在具有相同数量的匹配标签的组中没有顺序,但您可以为此添加第二个排序键。

只要每个实体都没有非常庞大的标签列表,这应该可以很好地工作。如果不需要,请不要计算“matched_tags”。如果您确实需要它,请考虑将其计算包装到子查询中,然后使用 ORDER BY 中的计算值而不是在那里重新计算。

您可能需要一台具有足够 RAM 的机器来容纳其中的 GiST 索引。如果UPDATE / INSERT 比率很低,您可以改用GIN 索引; GIN 的性能对于变化很小的数据更好,而对于变化很大的数据则非常差。

【讨论】:

  • 感谢您对进一步工作的精彩建议和启发!在我的应用程序中,使用 GiN 索引似乎更好,因为我将操作静态数据集,在相对较长的时间内更新整个数据集。
  • 您如何看待将整个数据存储在一张大表中的观点?通过几个表实现某种分片会更好还是没关系(或者相反,它会影响性能)?我真的很担心对具有大量高覆盖率标签的 1500 万个条目表执行此类查询的查询延迟......(P.S.:实际上我的盒子上有 49 Gb 的 RAM。)
【解决方案2】:

如果我正确理解您的架构,您可以将其全部合并到 1 个表中。 我为冗长的模式创建提前道歉,但我想向自己证明它实际上会使用索引。此示例使用 postgres,如果您安装 intarray 扩展,您可以在关系上创建 gist 或 gin 索引。我在 postgres 9.3 上测试过

create table keyword (id serial primary key, tag varchar, relation integer[]);

insert into keyword(id, tag,relation) values(1,'tag1',array[1]);
insert into keyword(id, tag,relation) values(2,'tag2',array[1,2]);
insert into keyword(id, tag,relation) values(3,'tag3',array[1,2,3]);
insert into keyword(id, tag,relation) values(4,'tag4',array[1,2,3,4]);
insert into keyword(id, tag,relation) values(5,'tag5',array[1,2,3,4,5]);
insert into keyword(id, tag,relation) values(6,'tag6',array[1,2,3,4,5,6]);
insert into keyword(id, tag,relation) values(7,'tag7',array[1,2,3,4,5,6,7]);
insert into keyword(id, tag,relation) values(8,'tag8',array[1,2,3,4,5,6,7,8]);
insert into keyword(id, tag,relation) values(9,'tag9',array[1,2,3,4,5,6,7,8,9]);
insert into keyword(id, tag,relation) values(10,'tag10',array[1,2,3,4,5,6,7,8,9,10]);
insert into keyword(id, tag,relation) values(11,'tag11',array[11]);
insert into keyword(id, tag,relation) values(12,'tag12',array[12]);
insert into keyword(id, tag,relation) values(13,'tag13',array[13]);
insert into keyword(id, tag,relation) values(14,'tag14',array[14]);
insert into keyword(id, tag,relation) values(15,'tag15',array[15]);
insert into keyword(id, tag,relation) values(16,'tag16',array[16,13,12]);
insert into keyword(id, tag,relation) values(17,'tag17',array[17,10,9,5,2,1]);
insert into keyword(id, tag,relation) values(18,'tag18',array[18,1,2,3]);
insert into keyword(id, tag,relation) values(19,'tag19',array[19,1]);
insert into keyword(id, tag,relation) values(20,'tag20',array[20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1]);
insert into keyword(id, tag,relation) values(21,'tag21',array[21]);
insert into keyword(id, tag,relation) values(22,'tag22',array[22]);
insert into keyword(id, tag,relation) values(23,'tag23',array[23]);
insert into keyword(id, tag,relation) values(24,'tag24',array[24]);
insert into keyword(id, tag,relation) values(25,'tag25',array[25]);
insert into keyword(id, tag,relation) values(26,'tag26',array[26]);
insert into keyword(id, tag,relation) values(27,'tag27',array[27]);
insert into keyword(id, tag,relation) values(28,'tag28',array[28]);
insert into keyword(id, tag,relation) values(29,'tag29',array[29]);
insert into keyword(id, tag,relation) values(30,'tag30',array[30]);
insert into keyword(id, tag,relation) values(31,'tag31',array[30]);
insert into keyword(id, tag,relation) values(32,'tag32',array[30]);
insert into keyword(id, tag,relation) values(33,'tag33',array[30]);
insert into keyword(id, tag,relation) values(34,'tag34',array[30]);
insert into keyword(id, tag,relation) values(35,'tag35',array[30]);
insert into keyword(id, tag,relation) values(36,'tag36',array[30]);
insert into keyword(id, tag,relation) values(37,'tag37',array[30]);
insert into keyword(id, tag,relation) values(38,'tag38',array[30]);
insert into keyword(id, tag,relation) values(39,'tag39',array[30]);
insert into keyword(id, tag,relation) values(40,'tag40',array[30]);
insert into keyword(id, tag,relation) values(41,'tag41',array[30]);
insert into keyword(id, tag,relation) values(42,'tag42',array[30]);
insert into keyword(id, tag,relation) values(43,'tag43',array[30]);
insert into keyword(id, tag,relation) values(44,'tag44',array[30]);
insert into keyword(id, tag,relation) values(45,'tag45',array[30]);
insert into keyword(id, tag,relation) values(46,'tag46',array[30]);
insert into keyword(id, tag,relation) values(47,'tag47',array[30]);
insert into keyword(id, tag,relation) values(48,'tag48',array[30]);
insert into keyword(id, tag,relation) values(49,'tag49',array[30]);
insert into keyword(id, tag,relation) values(50,'tag50',array[30]);
insert into keyword (id, tag) (select generate_series, 'tag'||generate_series from generate_series(51,500));

create index on keyword(array_length(relation,1));
/*Uncomment the line below if you have intarray installed */
/*create index on keyword using gist(relation);*/
analyze  keyword;

因此,要查找与其他标签有 5 个关系的所有元素,只需运行以下命令:

select * from keyword where array_length(relation,1)=5

要查找与标签 17 相关的所有元素,请运行以下命令:

select * from keyword where relation @> array[17]

关系数组列可能包含重复值,这会搞砸事情,因此您可以编写一个函数和一个检查约束来防止这种情况发生,或者在应用程序中编写此代码——检查约束可能会大大增加成本插入。

随意在 SQLFiddle 上使用这个架构,我在这里创建了架构:SqlFiddle

【讨论】:

  • 非常感谢您的参与,特别是 SQLfiddle 沙箱实验!尽管 Craig Ringer 在他的回答中提供的查询更充分地满足了问题的确切要求,但您提出了相同的 GiST+intarray 方法,这似乎是有效的解决方案!
  • @zavg 谢谢。不想提交重复的方法,但是当我开始研究答案时,还没有人回答。我想简洁是有好处的。不要忘记 array_length(relation,1) 上的索引,因为如果我正确理解问题,我相信它是解决方案的重要部分。祝你好运
【解决方案3】:

您提出的架构没有多大意义。您在称为实体的事物之间建立了 N:M 关系(相当令人困惑,因为这通常用于由关系数据库中的单个表表示的任何数据结构)。我假设在重述中丢失了一些东西,而您实际上的意思是您有三张桌子:

keywords {id, keyword}
entities {id, ....}
entity_words {keyword_id, entity_id}

显着改进此架构的唯一方法是将匹配计数非规范化到“实体”记录中:

UPDATE entities e
SET e.matches = (SELECT COUNT(DISTINCT ew.keyword_id)
   FROM entity_words ew
   WHERE ew.entity_id=e.id);

....虽然您还可以在关键字表上添加触发器以在关键字中的数据发生更改时更新相关实体记录,但当您必须首先拥有创建映射的机制时,这似乎有点过头了.

【讨论】:

  • 1) 我同意我的域的实际数据模式如果 M:N 但出于我的任务的目的不需要使用Entities 数据维护表。因此,在我的描述中,Entities 表对应于您的entitiy_words,而entities 实际上并不是必需的,因为我只能在id-s 级别上工作。 2)不幸的是,您的非规范化查询根本没有涵盖我的情况,因为它只存储链接到某个实体的标签数量并且没有回答我在任务中提出的问题......
猜你喜欢
  • 1970-01-01
  • 2019-07-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多