我有一个包含数百万条目的表“test”。每行包含一个浮点“特征”和一个“计数”这个特征在项目“id”中出现的频率。该表的主键是“id”和“feature”的组合,即每个项目可能具有多个功能。每个商品ID通常有几百到几千个要素条目。
create table test
(
id int not null,
feature double not null,
count int not null
);
任务是找到给定参考项目的500个最相似的项目。相似性以两个项目中相同特征值的数量来度量。我提出的查询在下面引用,但尽管正确使用索引,其执行计划仍然包含“using temporary”和“using filesort”,为我的用例提供了不可接受的性能。
select
t1.id,
t2.id,
sum( least( t1.count, t2.count )) as priority
from test as t1
inner join test as t2
on t2.feature = t1.feature
where t1.id = {some user supplied id value}
group by t1.id, t2.id
order by priority desc
limit 500;
有关如何改进的任何想法?可以修改模式并根据需要添加索引。
答案 0 :(得分:4)
使用当前架构,几乎无法改进此查询。
您已经拥有feature
的索引,这是您使用当前架构设计所能做到的最佳选择。
问题是更类似于不是订单关系。如果a
与b
更相似,那么c
与c
的相似性并不意味着a
与b
相似1}}。因此,您无法构建描述此关系的单个索引,并且需要单独为每个项执行此操作,这将使您的索引N^2
条目变长,其中N
是项目数。
如果您始终只需要顶级500
项,则可以将索引限制为该数字(在这种情况下,它将保留500 * N
项。)
MySQL
不支持索引或实体化视图,因此您必须自己完成:
创建一个这样的表:
CREATE TABLE similarity
(
id1 INT NOT NULL,
id2 INT NOT NULL,
similarity DOUBLE NOT NULL,
PRIMARY KEY (id1, id2),
KEY (id1, similarity)
)
每当您在表格中插入新功能时,请反映similarity
中的更改:
INSERT
INTO similarity
SELECT @newid, id,
LEAST(@newcount, count) AS ns
FROM test
WHERE feature = @newfeature
AND id <> @newid
ON DUPLICATE KEY UPDATE
SET similarity = similarity + ns;
INSERT
INTO similarity
SELECT @newid, id,
LEAST(@newcount, count) AS ns
FROM test
WHERE feature = @newfeature
AND id <> @newid
ON DUPLICATE KEY UPDATE
SET similarity = similarity + ns;
及时删除多余的相似之处:
DELETE s
FROM (
SELECT id1,
(
SELECT similarity
FROM similarity si
WHERE si.id1 = s.id1
ORDER BY
si.id1 DESC, si.similarity DESC
LIMIT 499, 1
) AS cs
FROM (
SELECT DISTINCT id1
FROM similarity
) s
) q
JOIN similarity s
ON s.id1 = q.id1
AND s.similarity < q.cs
查询您的数据:
SELECT id2
FROM similarity
WHERE id1 = @myid
ORDER BY
similarity DESC
LIMIT 500
答案 1 :(得分:3)
将浮点数作为主键(PK)的一部分是一个杀手。就此而言,它不应成为任何约束的一部分 - 唯一键(英国),外键(FK)等。
要提高SQL查询的性能,请尝试更改您的架构,如下所示:
CREATE TABLE test (
item_id INTEGER,
feature_id INTEGER,
count INTEGER );
CREATE TABLE features (
id INTEGER, feature_value double not null );
CREATE TABLE items (
id INTEGER, item_description varchar2(100) not null );
ALTER TABLE test ADD CONSTRAINT fk_test_item_id foreign key (item_id) references items(id);
ALTER TABLE test ADD CONSTRAINT fk_test_feature_id foreign key(feature_id) references features(id);
如上所示将测试表标准化,我将项目和功能分离到它自己的单独表中,这不仅仅是一个带有每个映射计数的映射表。
如果您现在触发前面已经解决过的SQL查询而几乎没有任何修改,您应该会看到SQL查询性能的重大/显着改进。
select t1.id, t2.id, sum( least( t1.count, t2.count )) as priority
from test as t1 inner join test as t2 on t2.feature_id = t1.feature_id
where t1.id = {some user supplied id value}
group by t1.id, t2.id
order by priority desc
limit 500;
干杯!
答案 2 :(得分:2)
一个优化是将项目本身从自联接中排除:
inner join test as t2
on t2.feature = t1.feature and t2.id <> t1.id
^^^^^^^^^^^^^^
要进一步加速,请在(feature, id, count)
上创建覆盖索引。
答案 3 :(得分:0)
我会从这开始...喜欢听到你正在看的表现。我认为你不需要最少(t1对t2计数)。如果您是第一次根据ID = {some value}来确定哪个位置,您显然会得到所有这些“功能”。然后通过自我联接到自己只关注匹配的“功能”,你得到一个计数。由于您按ID1和ID2分解,因此每个相应的“功能”将被计算一次。在这个查询结束时,因为我没有明确地将t2.ID等于{some user value},所以它的计数应该是t1中特征的精确计数,而其他任何东西都是你最接近的匹配
我会确保我有一个关于ID和FEATURE的索引。
select STRAIGHT_JOIN
t1.id,
t2.id,
count(*) as MatchedInBoth
from
test as t1,
test as t2
where
t1.id = {some user value}
and t1.feature = t2.feature
group by
t1.id,
t2.id
order by
MatchedInBoth desc
limit
500;
结果可能会提供类似
的内容t1 t2 MatchedInBoth
{user value} {user value} 275
{user value} Other ID 1 270
{user value} Other ID 2 241
{user value} Other ID 3 218
{user value} Other ID 4 197
{user value} Other ID 5 163, etc
答案 4 :(得分:-1)
你能把它打到一张桌子吗? Usinq子查询您可以避免连接,如果子查询更快,索引并执行一次,它将是一个胜利。像这样(未经测试)。
select
t2.id,
SUM( t2.count ) as priority
from test as t2
where t2.id = {some user supplied id value} AND
t2.count > (SELECT MIN(count) FROM test t1 WHERE id= {some user supplied value} ) AND
t2.feature IN (SELECT feature FROM test t1 WHERE id= {some user supplied value} )
group by t1.id
order by priority desc
limit 500;
如果不起作用,Mysql很难实现内部选择是常量表并将为每一行重新执行它们。将它们再次包装在选择中会强制执行常量表查找。这是一个黑客:
select
t1.id,
SUM( t2.count ) as priority
from test as t2
where t2.id = {some user supplied id value} AND
t2.count > (
SELECT * FROM (
SELECT MIN(count) FROM test t1 WHERE id= {some user supplied
value} ) as const ) AND
t2.feature IN ( SELECT * from (
SELECT feature FROM test t1 WHERE id= {some user supplied value}
) as const )
group by t1.id
order by priority desc
limit 500;