限制 1 加入 table?

limit 1 in joined table?

tables

明显而缓慢(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;