我正在寻找以下问题的架构解决方案:
问题的一般说明
我有很多不同的数据实体(大约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
- 关键字等进行手动搜索。应用水平。
问题
显然,这种方法会遇到两个问题:
Entities
表接收数据集的时间很长(即使使用索引); N
- 关键字在应用程序级别匹配。对于这两个问题,请考虑一般情况下,一个标记可以与 数百万 条目相关联。
如何有效处理这些问题?
答案 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);
....虽然你也可以在关键字表上添加一个触发器,以便在关键字中的数据发生变化时更新相关的实体记录,但是当你必须拥有一个首先创建映射的机制时,这似乎有点过头了。