You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化含BETWEEN的MySQL JOIN语句及索引失效问题

问题背景

现有两张数据库表,结构如下:

ip_info表

CREATE TABLE `ip_info` (
  `start_ip` int(10) unsigned NOT NULL,
  `end_ip` int(10) unsigned NOT NULL,
  `country_code` varchar(3) DEFAULT NULL,
  `country_name` varchar(255) DEFAULT NULL,
  `continent_code` varchar(3) DEFAULT NULL,
  `continent_name` varchar(255) DEFAULT NULL,
  `asn` int(10) unsigned DEFAULT NULL,
  `as_name` varchar(255) DEFAULT NULL,
  `as_domain` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`start_ip`,`end_ip`),
  KEY `country_code_idx` (`country_code`),
  KEY `asn_idx` (`asn`)
)

servers表

CREATE TABLE `servers` (
  `ipport` varchar(255) NOT NULL,
  `ip` varchar(255) NOT NULL,
  `port` int(11) NOT NULL,
  `ip_as_int` int(10) unsigned NOT NULL,
  `version` text NOT NULL,
  `protocol` int(11) NOT NULL,
  `online_count` int(11) NOT NULL,
  `max_count` int(11) NOT NULL,
  `description` text NOT NULL,
  `favicon` text DEFAULT NULL,
  `last_seen` int(11) NOT NULL,
  `cracked` tinyint(1) DEFAULT NULL,
  `joined_on` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`ip`,`port`),
  KEY `ip_as_int_idx` (`ip_as_int`)
)

执行以下查询(统计美国地区在线人数≥5的服务器数量)时耗时长达18秒:

SELECT count(*) FROM
    (SELECT * FROM servers AS d JOIN ip_info i ON d.ip_as_int BETWEEN i.start_ip AND i.end_ip WHERE i.country_code = "us") 
AS s WHERE (s.online_count > 5) ;

查询执行计划如下:

+------+-------------+-------+------+--------------------------+------------------+---------+-------+---------+------------------------------------------------+
| id   | select_type | table | type | possible_keys            | key              | key_len | ref   | rows    | Extra                                          |
+------+-------------+-------+------+--------------------------+------------------+---------+-------+---------+------------------------------------------------+
|    1 | SIMPLE      | i     | ref  | PRIMARY,country_code_idx | country_code_idx | 15      | const |  761917 | Using where; Using index                       |
|    1 | SIMPLE      | d     | ALL  | ip_as_int_idx            | NULL             | NULL    | NULL  | 1035230 | Range checked for each record (index map: 0x2) |
+------+-------------+-------+------+--------------------------+------------------+---------+-------+---------+------------------------------------------------+

可见ip_as_int_idx索引未被使用,导致MySQL对servers表进行全表扫描,求优化方案。


原因分析

执行计划中Range checked for each record说明:MySQL无法提前确定d.ip_as_int需要匹配的固定范围,而是要对每个ip_info(美国地区的IP段)逐一检查servers表的ip_as_int是否在该段内,这种情况下无法有效利用ip_as_int_idx索引,只能走全表扫描。

同时原查询先关联两张表再过滤online_count,会导致关联的数据量过大,进一步拖慢速度。


优化方案

1. 调整查询逻辑,先过滤再关联

先筛选出online_count>5的服务器,再关联IP信息表,减少关联的数据量:

SELECT COUNT(*)
FROM servers d
JOIN ip_info i ON d.ip_as_int BETWEEN i.start_ip AND i.end_ip
WHERE i.country_code = 'us'
  AND d.online_count > 5;

更高效的写法是用子查询先提取需要的字段,避免不必要的列参与关联:

SELECT COUNT(*)
FROM (SELECT ip_as_int FROM servers WHERE online_count > 5) d
JOIN ip_info i ON d.ip_as_int BETWEEN i.start_ip AND i.end_ip
WHERE i.country_code = 'us';

2. 优化ip_info表的索引

创建包含country_code、start_ip、end_ip的覆盖索引,让MySQL可以直接通过索引完成过滤和范围匹配,无需回表查询其他字段:

CREATE INDEX idx_country_ip_range ON ip_info (country_code, start_ip, end_ip);

该索引会先按country_code分组,同一国家的IP段按start_ip排序,能大幅提升IP范围匹配的效率。

3. 预计算存储国家信息(最优方案,适合高频查询)

如果该查询非常频繁,可在servers表中新增country_code字段,提前将服务器对应的国家代码写入,后续查询直接过滤即可:

-- 新增字段
ALTER TABLE servers ADD COLUMN country_code VARCHAR(3);

-- 一次性初始化数据
UPDATE servers d
JOIN ip_info i ON d.ip_as_int BETWEEN i.start_ip AND i.end_ip
SET d.country_code = i.country_code;

-- 后续查询直接执行,速度极快
SELECT COUNT(*) FROM servers WHERE country_code = 'us' AND online_count > 5;

注意:如果服务器IP会变动,需要配合定时任务或触发机制更新country_code字段,保证数据一致性。


内容的提问来源于stack exchange,提问作者Damian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 20:26:07