mySQL:是否可以更快地进行此查询?

时间:2010-11-29 18:01:30

标签: sql mysql optimization query-optimization

我有一个包含数百万条目的表“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;

有关如何改进的任何想法?可以修改模式并根据需要添加索引。

5 个答案:

答案 0 :(得分:4)

使用当前架构,几乎无法改进此查询。

您已经拥有feature的索引,这是您使用当前架构设计所能做到的最佳选择。

问题是更类似于不是订单关系。如果ab更相似,那么cc的相似性并不意味着ab相似1}}。因此,您无法构建描述此关系的单个索引,并且需要单独为每个项执行此操作,这将使您的索引N^2条目变长,其中N是项目数。

如果您始终只需要顶级500项,则可以将索引限制为该数字(在这种情况下,它将保留500 * N项。)

MySQL不支持索引或实体化视图,因此您必须自己完成:

  1. 创建一个这样的表:

    CREATE TABLE similarity
            (
            id1 INT NOT NULL,
            id2 INT NOT NULL,
            similarity DOUBLE NOT NULL,
            PRIMARY KEY (id1, id2),
            KEY (id1, similarity)
            )
    
  2. 每当您在表格中插入新功能时,请反映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;
    
  3. 及时删除多余的相似之处:

    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
    
  4. 查询您的数据:

    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;