【发布时间】:2016-02-27 22:51:52
【问题描述】:
非常简单的问题,但很难找到解决方案。 具有 2,498,739 行的地址表具有 min_ip 和 max_ip 字段。这些是用于过滤的表格的核心锚点。
查询很简单。
SELECT *
FROM address a
WHERE min_ip < value
AND max_ip > value;
因此为 min_ip 和 max_ip 创建索引以使查询更快是合乎逻辑的。
为以下内容创建索引。
CREATE INDEX ip_range ON address (min_ip, max_ip) USING BTREE;
CREATE INDEX min_ip ON address (min_ip ASC) USING BTREE;
CREATE INDEX max_ip ON address (max_ip DESC) USING BTREE;
我确实尝试只创建第一个选项(min_ip 和 max_ip 的组合),但它不起作用,所以我准备了至少 3 个索引,以便为 MySQL 提供更多用于索引选择的选项。 (请注意,此表几乎是静态的,更像是一个查找表)
+------------------------+---------------------+------+-----+---------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+------------------------+---------------------+------+-----+---------------------+-----------------------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| network | varchar(20) | YES | | NULL | |
| min_ip | int(11) unsigned | NO | MUL | NULL | |
| max_ip | int(11) unsigned | NO | MUL | NULL | |
+------------------------+---------------------+------+-----+---------------------+-----------------------------+
现在,应该直接以 min_ip 和 max_ip 作为过滤条件来查询表。
EXPLAIN
SELECT *
FROM address a
WHERE min_ip < 2410508496
AND max_ip > 2410508496;
查询执行了大约 0.120 到 0.200 秒。但是,在负载测试中,查询会迅速降低性能。 MySQL 服务器 CPU 使用率飙升至 100% CPU 使用率仅在几个同时查询和性能迅速下降并且没有扩展。 mysql 服务器上的慢查询在 10 秒或更长时间内打开,最终在负载测试几秒钟后,选择查询显示在日志中。 所以我用explain检查了查询,发现它没有使用索引。
解释计划结果
id select_type table type possible_keys key key_len ref rows Extra
------ ----------- ------ ------ ---------------------- ------ ------- ------ ------- -------------
1 SIMPLE a ALL ip_range,min_ip,max_ip (NULL) (NULL) (NULL) 2417789 Using where
有趣的是,它能够将 ip_range、ip_min 和 ip_max 确定为潜在索引,但从未使用其中任何一个,如 key 列中所示。 我知道我可以使用 FORCE INDEX 并尝试对其使用说明计划。
EXPLAIN
SELECT *
FROM address a
FORCE INDEX (ip_range)
WHERE min_ip < 2410508496
AND max_ip > 2410508496;
用 FORCE INDEX 结果解释计划
id select_type table type possible_keys key key_len ref rows Extra
------ ----------- ------ ------ ------------- -------- ------- ------ ------- -----------------------
1 SIMPLE a range ip_range ip_range 4 (NULL) 1208894 Using index condition
使用 FORCE INDEX,是的,它使用 ip_range 索引作为键,并且行显示查询中不使用 FORCE INDEX 的子集,即 2,417,789 中的 1,208,894。 所以肯定,使用索引应该有更好的性能。 (除非我误解了解释结果)
但更有趣的是,经过几次测试,我发现在某些情况下,即使没有 FORCE INDEX,MySQL 也确实使用索引。而我的观察是,当值较小时,它确实使用索引。
EXPLAIN
SELECT *
FROM address a
WHERE min_ip < 508496
AND max_ip > 508496;
解释结果
id select_type table type possible_keys key key_len ref rows Extra
------ ----------- ------ ------ ---------------------- -------- ------- ------ ------ -----------------------
1 SIMPLE a range ip_range,min_ip,max_ip ip_range 4 (NULL) 1 Using index condition
所以,让我感到困惑的是,根据传递给选择查询的值,MySQL 决定何时使用索引以及何时不使用索引。 我无法想象确定何时在传递给查询的某个值上使用索引的基础是什么。我明白 如果没有适合 WHERE 条件的匹配索引,则可能不使用索引,但在这种情况下,很明显 ip_range 索引 是一个基于 min_ip 的索引,max_ip 列适合这种情况下的 WHERE 条件。
但我遇到的更大问题是,其他查询呢?我必须去大规模测试这些查询吗? 但即便如此,随着数据的增长,我是否可以依赖并期望 MySQL 使用索引? 是的,我总是可以使用 FORCE INDEX 来确保它使用索引。但这不是适用于所有数据库的标准 SQL。 ORM 框架在生成 SQL 时可能无法支持 FORCE INDEX 语法,并且它将查询与索引名称紧密耦合。
不确定是否有人遇到过这个问题,但这对我来说似乎是一个非常大的问题。
【问题讨论】:
-
这看起来像一个基数问题。当返回的行大约是表的 30% 或更多时,mysql 将决定表扫描更好,忽略索引。索引仅用于返回一个小的行的比例。您的第一个查询返回 1208894 行,第二个只有 1 行
-
如果基数较低,MySQL 将不会使用索引。基数是告诉您数据集中有多少唯一值的数字。 PK的基数是1,这是最大值。 MySQL 将此作为优化步骤。它确定索引是否会减少所需的查找量。
-
感谢您的快速回复。在我看来,这是特定于 MySQL 实现的。不确定我是否关注基数,但 MySQL 如何确定基数?当我使用 2,410,508,496 的值时,它不使用索引,当我使用 508,496 时,它使用的索引很奇怪。
-
如果我的数据库中的所有值都大于 2,410,508,496,索引应该告诉 MySQL 没有找到记录并跳过所有内容,因为 WHERE 条件表示 min_ip
-
具体细节是特定于实现的,但所有 RDBMS 的原理都是相同的。为每个索引保留统计信息,并尝试猜测哪个搜索条件索引组合在表中出现的次数最少。它使用它来选择要使用的索引,如果它们都不能正常工作(超过 30% 的行),它只会扫描所有记录。因为是统计数据,所以不一定总能猜对。
标签: mysql database performance indexing