我正在MySQL中进行以下查询,以查找会话ip地址的经/纬度,但是在5或6个会话之后,查询需要几分钟才能返回(如果有的话)。
ip2loc表有290万行。
select sessions.ip,
ip2loc.region_name, ip2loc.city_name,
ip2loc.latitude,
ip2loc.longitude,
count(sessions.ip) as count
from ip2location.ip2loc, sessions
where INET_ATON(sessions.ip) between ip_from and ip_to
group by ip
ip2loc表具有以下索引:
KEY `idx_ip_from` (`ip_from`),
KEY `idx_ip_to` (`ip_to`),
KEY `idx_ip_from_to` (`ip_from`,`ip_to`)
有没有更好的方法来构造此查询,这样它就不会花很多时间运行?
答案 0 :(得分:0)
QuerySnapshot
确保在表会话列ip上也有索引
您可以改善复合索引,为select所需的列添加冗余
select sessions.ip,
ip2loc.region_name,
ip2loc.city_name,
ip2loc.latitude,
ip2loc.longitude,
count(sessions.ip) as count
from ip2location.ip2loc
INNER JOIN sessions ON INET_ATON(sessions.ip) between ip2loc.ip_from and. ip2locip_to
group by sessions.ip
通过这种方式,查询可以检索索引中的所有信息,而无需访问表数据
出于可读性考虑,您不应使用基于where的旧的隐式联接sintax,而应使用基于join和ON子句的显式联接sintax
答案 1 :(得分:0)
问题:
INET_ATON(sessions.ip) between ip_from and ip_to
等效于
INET_ATON(sessions.ip) >= ip_from
AND
INET_ATON(sessions.ip) <= ip_to
此条件不能使用sessions
表中的索引,因为sessions.ip
被包装在函数调用中。
它可以使用位置表中的索引-但只能使用第一个关键部分。无法在两个不同的关键部分上进行两次范围扫描(>=
和<=
)。引擎可以使用(ip_from, ip_to)
上的索引,但需要读取sessions
表中每一行的索引中的所有行的一半(平均145万行) 。引擎甚至可能决定根本不使用索引。因此,您最终得到了两个表的完全连接。
您可以做的第一个优化是缩小sessions
表中的行数,将GROUP BY查询包装到子查询中:
select s.ip,
l.region_name,
l.city_name,
l.latitude,
l.longitude,
s.count
from (
select ip, INET_ATON(s.ip) ip_bin, count(*) as count
from sessions
group by ip
) s
join ip2location l on s.ip_bin between ip_from and ip_to
如果仍然太慢,则可以尝试将子查询结果存储到索引的临时表中:
create temporary table tmp_session_ips(
ip_bin int unsigned primary key,
ip varchar(15) not null,
`count` int unsigned
)
select ip, INET_ATON(s.ip) ip_bin, count(*) as count
from sessions
group by ip
order by ip_bin
;
select s.ip,
l.region_name,
l.city_name,
l.latitude,
l.longitude,
s.count
from tmp_session_ips s
join ip2location l on s.ip_bin between ip_from and ip_to
这样,可以将临时表中的PK(ip_bin
)用于连接。但是-这是理论。以我的经验,MySQL在优化联接的范围条件方面做得很差。现在,新版本可能会更好。