我们有几种类型的表,每个表大约有3000万到8000万行。 我们感兴趣的是在表上运行分析,但查询需要很长时间(查询需要10分钟以上)才能执行。 SQL大师可以发现我们可以应用的任何明显优化来加快查询执行吗?
URL包含有关sql架构的信息,
样本行
以及我们想要在最底层执行的查询。
我们还在userid
上添加了一个索引,但查询仍然需要很长时间。
http://pastebin.com/raw.php?i=pn9Kyg2z
用mariadb上的innodb创建的表。服务器已将12G分配给缓冲池
答案 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
的索引而1>} ,则数据量不是很大,那么这可能会有更好的效果。
编辑:
正如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。)
我坚持建议添加多列索引(替换单列索引)。