限制 1 加入 table?
limit 1 in joined table?
tables
ip2country
有 250k 行(按升序插入的 ip 范围 from_ip)
sessions
有 50 行
明显而缓慢(2.687 秒):
SELECT
s.*,
ip.country
FROM
sessions s
JOIN ip2country ip ON s.ip_addr BETWEEN ip.from_ip AND ip.to_ip
虽然这本身很快(0.031 秒):
SELECT
*
FROM
ip2country
WHERE
from_ip >= 387703808
LIMIT 1
所以本质上问题归结为能够在加入的 table 中使用 LIMIT。这可以做到吗,它会是什么样子? (MySQL 5.7.24)
这是一个类似的例子:
我有一个具有 100 个 IP(32 位整数)的 table 和一个具有 1M IP 范围的 table。 (请参阅下面的架构和示例数据。)
以下查询与您的类似:
select *
from ips i join ip_ranges r
on i.ip between r.ip_from and r.ip_to
return 100 个具有相应范围的 IP 需要 9.6 秒。即每个 IP 100 毫秒。如果我只搜索一个IP
select *
from ip_ranges r
where 555555555 between ip_from and ip_to
大约需要 100 毫秒(符合预期)。请注意,对于 IP = 1,我将在 "zero" 时间内获得结果,但对于 IP = 999,999,999,我将等待 200 毫秒。所以 100 毫秒是平均值。
添加 LIMIT 1
在这里没有帮助。但是结合 ORDER BY ip_from DESC
我得到的结果是 "zero time".
现在我可以在子查询中尝试 运行 每个 IP LIMIT 1
:
select i.ip
, (
select ip_from
from ip_ranges r
where i.ip between r.ip_from and r.ip_to
order by r.ip_from desc
limit 1
) as ip_from
from ips i
但是 MySQL(在我的例子中是 5.6)在这里做得很差,执行需要 13 秒。
所以我们所能做的就是获取所有 IP 并为每个 IP 执行一个查询。这至少会比 10 秒快。
另一种方法是生成一个 UNION ALL 查询,每个 IP 有一个子查询。您可以在您的应用程序中执行此操作,也可以直接在 SQL 中使用动态准备语句执行此操作:
set @subquery = '(
select {ip} as ip, r.*
from ip_ranges r
where {ip} between ip_from and ip_to
order by ip_from desc
limit 1
)';
set session group_concat_max_len = 1000000000;
set @sql = (select group_concat(replace(@subquery, '{ip}', ip) separator 'union all') from ips);
prepare stmt from @sql;
execute stmt;
此查询的执行时间不到 1 毫秒。
测试模式和数据
create table ips(
ip int unsigned primary key
);
insert into ips(ip)
select floor(rand(1) * pow(10, 9))
from seq1m s
limit 100
;
create table ip_ranges(
ip_from int unsigned not null,
ip_to int unsigned not null,
primary key (ip_from, ip_to)
);
insert into ip_ranges
select (s.seq - 1) * 1000 as ip_from
, s.seq * 1000 - 1 as ip_to
from seq1m s
limit 1000000
;
seq1m
是一个 table 1M 序号。您可以使用
创建它
create table seq1m (seq int auto_increment primary key);
insert into seq1m (seq)
select null
from information_schema.COLUMNS a
, information_schema.COLUMNS b
limit 1000000;
tables
ip2country
有 250k 行(按升序插入的 ip 范围 from_ip)sessions
有 50 行
明显而缓慢(2.687 秒):
SELECT
s.*,
ip.country
FROM
sessions s
JOIN ip2country ip ON s.ip_addr BETWEEN ip.from_ip AND ip.to_ip
虽然这本身很快(0.031 秒):
SELECT
*
FROM
ip2country
WHERE
from_ip >= 387703808
LIMIT 1
所以本质上问题归结为能够在加入的 table 中使用 LIMIT。这可以做到吗,它会是什么样子? (MySQL 5.7.24)
这是一个类似的例子:
我有一个具有 100 个 IP(32 位整数)的 table 和一个具有 1M IP 范围的 table。 (请参阅下面的架构和示例数据。)
以下查询与您的类似:
select *
from ips i join ip_ranges r
on i.ip between r.ip_from and r.ip_to
return 100 个具有相应范围的 IP 需要 9.6 秒。即每个 IP 100 毫秒。如果我只搜索一个IP
select *
from ip_ranges r
where 555555555 between ip_from and ip_to
大约需要 100 毫秒(符合预期)。请注意,对于 IP = 1,我将在 "zero" 时间内获得结果,但对于 IP = 999,999,999,我将等待 200 毫秒。所以 100 毫秒是平均值。
添加 LIMIT 1
在这里没有帮助。但是结合 ORDER BY ip_from DESC
我得到的结果是 "zero time".
现在我可以在子查询中尝试 运行 每个 IP LIMIT 1
:
select i.ip
, (
select ip_from
from ip_ranges r
where i.ip between r.ip_from and r.ip_to
order by r.ip_from desc
limit 1
) as ip_from
from ips i
但是 MySQL(在我的例子中是 5.6)在这里做得很差,执行需要 13 秒。
所以我们所能做的就是获取所有 IP 并为每个 IP 执行一个查询。这至少会比 10 秒快。
另一种方法是生成一个 UNION ALL 查询,每个 IP 有一个子查询。您可以在您的应用程序中执行此操作,也可以直接在 SQL 中使用动态准备语句执行此操作:
set @subquery = '(
select {ip} as ip, r.*
from ip_ranges r
where {ip} between ip_from and ip_to
order by ip_from desc
limit 1
)';
set session group_concat_max_len = 1000000000;
set @sql = (select group_concat(replace(@subquery, '{ip}', ip) separator 'union all') from ips);
prepare stmt from @sql;
execute stmt;
此查询的执行时间不到 1 毫秒。
测试模式和数据
create table ips(
ip int unsigned primary key
);
insert into ips(ip)
select floor(rand(1) * pow(10, 9))
from seq1m s
limit 100
;
create table ip_ranges(
ip_from int unsigned not null,
ip_to int unsigned not null,
primary key (ip_from, ip_to)
);
insert into ip_ranges
select (s.seq - 1) * 1000 as ip_from
, s.seq * 1000 - 1 as ip_to
from seq1m s
limit 1000000
;
seq1m
是一个 table 1M 序号。您可以使用
create table seq1m (seq int auto_increment primary key);
insert into seq1m (seq)
select null
from information_schema.COLUMNS a
, information_schema.COLUMNS b
limit 1000000;