在具有3列的3600万行表上优化SQL内连接

时间:2015-05-29 20:25:20

标签: mysql sql join optimization

我们有几种类型的表,每个表大约有3000万到8000万行。 我们感兴趣的是在表上运行分析,但查询需要很长时间(查询需要10分钟以上)才能执行。 SQL大师可以发现我们可以应用的任何明显优化来加快查询执行吗?

URL包含有关sql架构的信息, 样本行 以及我们想要在最底层执行的查询。 我们还在userid上添加了一个索引,但查询仍然需要很长时间。 http://pastebin.com/raw.php?i=pn9Kyg2z 用mariadb上的innodb创建的表。服务器已将12G分配给缓冲池

3 个答案:

答案 0 :(得分:3)

为了一个简单的例子,我将从this SQLFiddle开始工作。

缩减架构:

CREATE TABLE visits(
  user_id INTEGER NOT NULL,
  minute_id INTEGER NOT NULL,
  visit_id INTEGER NOT NULL
);

CREATE INDEX visits_user_id_idx ON visits(user_id);
CREATE INDEX visits_minute_id_idx ON visits(minute_id);
CREATE INDEX visits_visit_id_idx ON visits(visit_id);

您目前正在使用子查询,如下所示:

SELECT COUNT(v1.visit_id)
FROM visits v1
INNER JOIN (SELECT * FROM visits WHERE minute_id BETWEEN 100 AND 200) v2 ON v2.user_id = v1.user_id
WHERE v1.minute_id BETWEEN 600 AND 700;

你不需要带有临时表的内联子查询 - 这会导致数据库产生超过它需要的更多数据,这会减慢速度。

在平面查询中可以实现相同的逻辑:

SELECT COUNT(v1.visit_id)
FROM visits v1
INNER JOIN visits v2 ON v2.user_id = v1.user_id
WHERE v2.minute_id BETWEEN 100 AND 200
AND v1.minute_id BETWEEN 600 AND 700;

小提琴链接包含EXPLAIN个结果,这些结果表明数据库引擎将更容易处理这个问题,因为保留了较少的临时数据,并且因为所需的索引更简单。

答案 1 :(得分:1)

我正在使用Rutter的简化。

这似乎是您想要的查询:

SELECT COUNT(v1.visit_id)
FROM visits v1 INNER JOIN
     visits v2
     ON v2.user_id = v1.user_id
WHERE v2.minute_id BETWEEN 100 AND 200 AND v1.minute_id BETWEEN 600 AND 700;

这似乎是在两个时间段内访问过的用户的访问次数。

您也可以将其表达为:

select sum(numvisit)
from (select user_id, count(*) as numvisit
      from visits v
      where v.minute_id BETWEEN 100 AND 200 or v.minute_id BETWEEN 600 AND 700
      group by user_id
      having sum(v.minute_id BETWEEN 100 AND 200) > 0 and
             sum(v.minute_id BETWEEN 600 AND 700) > 0
     ) uv;

如果MySQL在minute_id上使用where的索引而} ,则数据量不是很大,那么这可能会有更好的效果。

编辑:

正如Spencer非常正确地指出的那样,第二个查询获得了两个时间段内访问次数的数量。这似乎很有用。获得两个时间段内访问次数的用户数量似乎也很有用,这两个时间段为count(*)而不是sum(numvisits)

每个用户每个时期的访问次数的乘积似乎不太可能是期望的结果。但是,如果那是你真正想要的,那么:

select sum(cnt1 * cnt2)
from (select user_id, count(*) as numvisit,
             sum(v.minute_id BETWEEN 100 AND 200) as cnt1,
             sum(v.minute_id BETWEEN 600 AND 700)
      from visits v
      where v.minute_id BETWEEN 100 AND 200 or v.minute_id BETWEEN 600 AND 700
      group by user_id
      having sum(v.minute_id BETWEEN 100 AND 200) > 0 and
             sum(v.minute_id BETWEEN 600 AND 700) > 0
     ) uv;

将是查询。但是,为什么你想要这个特殊价值?

答案 2 :(得分:0)

修改

MariaDB的EXPLAIN输出不显示派生表,因此刮开该部分。 (较早版本的MySQL 始终为内联视图创建派生表。)因此,请跳过有关内联视图查询和派生表的内容...

查看有关添加多列索引(以及删除单列索引)的建议。

可能,您大部分时间都在扫描派生表;或者,实现派生表(并在其上创建索引,如果您的MariaDB版本索引派生表。)

我不明白你为什么需要内联视图。

我认为这会产生相同的结果集:

 SELECT COUNT(1)
   FROM td222_visits_ppp v
   JOIN td222_visits_ppp l
     ON l.userid = v.userid
    AND l.minuteid >= 23704140
    AND l.minuteid <= 23790480
  WHERE p.minuteid  > 23790480
    AND p.minuteid <= 23878320

(您确定查询是否获得了您想要的数字...特定时间段内特定用户ID的每一行都会加入到不同时间段内同一用户ID的每一行?)

各个列的索引对此查询不会有太多帮助。此查询的最合适的索引将是:

... ON td222_visits_ppp (user_id, minuteid)

(注意:这会使user_id列上的索引变得多余,也就是说,当前使用user_id上现有索引的任何查询都可以使用此新索引,该索引具有user_id }作为主要栏目。)

在创建新索引后,我对此查询中的EXPLAIN感兴趣。

(我们希望在v上使用完整密钥长度12和“额外”列中的Using index执行范围扫描操作在p上,我们可能会在minuteid上看到索引的范围扫描操作。)

如果我们有另一个索引

... ON td222_visits_ppp (minuteid, userid)

这会使得只有minid的索引变得冗余,并且也可以从索引中满足查询(无需访问底层数据页面来查找userid。)

修改

我只是查看了MariaDB的EXPLAIN输出...而且我们没有看到&#34;派生表&#34;在那里,我抓住了关于实现和扫描或索引派生表所花费的时间的答案。 (对于内联视图查询,我们熟悉的旧版MySQL。)

我坚持建议添加多列索引(替换单列索引)。