多个关键字搜索的反向索引

时间:2013-11-24 22:23:46

标签: mysql database performance postgresql optimization

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

问题的一般说明

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

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

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

天真的方法

我遇到的天真解决方案是使用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 - 关键字,N-1 - 关键字等进行手动搜索。应用水平。

问题

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

  1. 从十亿条Entities表接收数据集的时间很长(即使使用索引);
  2. 长时间执行应用级别搜索N - 关键字在应用程序级别匹配。
  3. 对于这两个问题,请考虑一般情况下,一个标记可以与 数百万 条目相关联。

    如何有效处理这些问题?

3 个答案:

答案 0 :(得分:3)

我会使用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的性能对于变化很小的数据更好,对于变化很大的数据非常糟糕。

答案 1 :(得分:1)

如果我正确理解您的架构,您可以将它全部合并到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

答案 2 :(得分:0)

你提出的架构没有多大意义。你称之为实体的东西之间存在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);

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