【问题标题】:MySQL - Basic index slowing down simple aggregate queryMySQL - 基本索引减慢简单聚合查询
【发布时间】:2018-04-09 06:17:00
【问题描述】:

我有一个简单的表来存储流量数据:

CREATE TABLE `domain_traffic` (
  `dtraff_id` int(10) UNSIGNED NOT NULL,
  `domain_id` int(10) UNSIGNED NOT NULL,
  `dtraff_time` bigint(20) UNSIGNED NOT NULL,
  `dtraff_web` bigint(20) UNSIGNED DEFAULT '0',
  `dtraff_ftp` bigint(20) UNSIGNED DEFAULT '0',
  `dtraff_mail` bigint(20) UNSIGNED DEFAULT '0',
  `dtraff_pop` bigint(20) UNSIGNED DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

ALTER TABLE `domain_traffic`
  ADD PRIMARY KEY (`dtraff_id`),
  ADD KEY `domain_id` (`domain_id`);

ALTER TABLE `domain_traffic`
  MODIFY `dtraff_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;

通过强制使用 domain_id 索引(MySQL 默认执行的操作)运行以下查询大约需要 12 秒:

SELECT SQL_NO_CACHE
    SUM(dtraff_web) as web,
    SUM(dtraff_ftp) as ftp,
    SUM(dtraff_mail) as mail,
    SUM(dtraff_pop) as pop
FROM domain_traffic FORCE INDEX (domain_id)
WHERE domain_id = 150

但是

上面要求 MySQL 忽略 domain_id 索引的相同查询只需要大约 2 秒(无论如何这仍然很糟糕):

SELECT SQL_NO_CACHE
    SUM(dtraff_web) as web,
    SUM(dtraff_ftp) as ftp,
    SUM(dtraff_mail) as mail,
    SUM(dtraff_pop) as pop
FROM domain_traffic IGNORE INDEX (domain_id)
WHERE domain_id = 150

我真的很惊讶这样的结果,我真的很想知道为什么会这样......

EXPLAIN 两个查询:

使用 domain_id 索引:

+------+-------------+----------------+------+---------------+-----------+---------+-------+---------+-------+
| id   | select_type | table          | type | possible_keys | key       | key_len | ref   | rows    | Extra |
+------+-------------+----------------+------+---------------+-----------+---------+-------+---------+-------+
|    1 | SIMPLE      | domain_traffic | ref  | domain_id     | domain_id | 4       | const | 2069312 |       |
+------+-------------+----------------+------+---------------+-----------+---------+-------+---------+-------+

忽略 domain_id 索引

+------+-------------+----------------+------+---------------+------+---------+------+---------+-------------+
| id   | select_type | table          | type | possible_keys | key  | key_len | ref  | rows    | Extra       |
+------+-------------+----------------+------+---------------+------+---------+------+---------+-------------+
|    1 | SIMPLE      | domain_traffic | ALL  | NULL          | NULL | NULL    | NULL | 4138625 | Using where |
+------+-------------+----------------+------+---------------+------+---------+------+---------+-------------

记录在案:

  1. MySQL 供应商:MariaDB 10.1
  2. ID 为 150 的域的行数:4156659

有什么解释和建议吗?

谢谢。

没有优化提示的结果(遵循@Bill Karwin 的回答):

MariaDB [imscp]> EXPLAIN
  SELECT SQL_NO_CACHE
          SUM(dtraff_web) as web, SUM(dtraff_ftp) as ftp,
          SUM(dtraff_mail) as mail, SUM(dtraff_pop) as pop
      FROM domain_traffic WHERE domain_id = 150;
+------+-------------+----------------+------+---------------+-----------+---------+-------+---------+-------+
| id   | select_type | table          | type | possible_keys | key       | key_len | ref   | rows    | Extra |
+------+-------------+----------------+------+---------------+-----------+---------+-------+---------+-------+
|    1 | SIMPLE      | domain_traffic | ref  | domain_id     | domain_id | 4       | const | 2069312 |       |
+------+-------------+----------------+------+---------------+-----------+---------+-------+---------+-------+
1 row in set (0.00 sec)

MariaDB [imscp]> SELECT SQL_NO_CACHE
      SUM(dtraff_web) as web, SUM(dtraff_ftp) as ftp,
      SUM(dtraff_mail) as mail, SUM(dtraff_pop) as pop
  FROM domain_traffic WHERE domain_id = 150;
+--------------+--------------+--------------+--------------+
| web          | ftp          | mail         | pop          |
+--------------+--------------+--------------+--------------+
| 105989792928 | 106045788277 | 105954990092 | 105942540350 |
+--------------+--------------+--------------+--------------+
1 row in set (8.53 sec)

【问题讨论】:

  • 这个问题与stackoverflow.com/questions/46757781/mysql-not-using-index/… 非常相似,但并不完全重复
  • 这 4 个数字几乎不可能完全相同。建议您检查其有效性。 (mail 和 pop 是有意义的,但不是其他的。)
  • @RickJames 这些非常相似,因为我使用 rand 创建了用于测试目的的行。
  • @Nuxwin - 呵呵,当然。

标签: mysql indexing


【解决方案1】:

当您有忽略索引的 EXPLAIN 时,它显示它运行表扫描(类型:ALL)。检查的行数约为 4.1m(无论如何这只是一个粗略的估计,而不是准确的计数)。

您澄清说,大约有 410 万行 domain_id = 150。因此,实际上表中的每一行都符合您的 WHERE 条件。

想想一本书后面的索引。为什么它不包含诸如“the”或“and”之类的词的条目?因为这些词几乎出现在每一页上,将它们编入索引是浪费时间,并使用索引来查找这些常见词的出现,翻到相应的页面,然后再翻回索引以查找下一个出现在第 2 页,依此类推。

这与 MySQL 中的二级索引相同。如果优化器检测到您搜索的给定值太常见,它会跳过索引并只进行表扫描。当索引无法有效地缩小搜索范围以使其有价值时,读取索引时这样做更容易。

在实践中,我观察到当值出现在表中 21-25% 的行上时,优化器会跳过使用索引。通常这是一个很好的电话。很少有必要使用 FORCE INDEX 告诉优化器您不希望不惜一切代价进行表扫描。但这种情况很少见。

我的建议是:让优化器完成它的工作。它通常会根据查询逻辑频率做出是否使用索引的好决定您正在搜索的特定值。


你的评论:

如果您的生产数据允许 WHERE 条件选择表的少数子集,那么优化器应该决定值得使用索引。优化器的目标之一是减少 InnoDB 需要读取的检查行数

这是一个很好的例子,说明了为什么您需要使用模拟生产数据的数据进行测试。拥有不同数据值的正确比率有助于您进行实际的查询优化器测试。

还要确保您不时使用ANALYZE TABLE 以确保 InnoDB 具有有关索引中数据分布的当前统计信息。我已经看到通过运行 ANALYZE TABLE 非常简单地修复了奇怪的索引行为的情况。即使您的桌子很大,这也是一个快速的操作。

这不必非常频繁,但如果索引中值的分布发生显着变化(例如,如果您执行主要的批量插入或批量删除),则值得在之后执行 ANALYZE TABLE。

【讨论】:

  • 这是示例。优化器(不强制使用索引)仍在使用它。请参阅我的答案的底部。我添加了没有索引提示的语句。是的,这里所有记录都属于 ID 为 150 的域,但在有多个域并且我们还使用范围(介于)检索给定时间段的数据的生产环境中,情况并非如此。
【解决方案2】:

你实际上是双向计时的吗?当它忽略索引时,它可能运行得更快。 (比尔解释了原因。)

如果您想加快查询速度,请继续阅读...

问题在于满足查询所需的行是分散的——或者随机排列,或者中间有垃圾,或者两者兼而有之。解决方案是重新排列数据,以便您只需要读取所需的数据,甚至避免使用二级索引。

`dtraff_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`domain_id` int(10) UNSIGNED NOT NULL,
...
PRIMARY KEY(domain_id, dtraff_time, dtraff_id)
INDEX(dtraff_id)

PRIMARY KEY(在 InnoDB 中)强制数据按所需顺序排列。在您的情况下,这会将所有 2M 行 WHERE domain_id = 150 聚集在一起,并让您阅读它们,即使 没有使用二级索引所需的额外查找.

INDEX(dtraff_id) 是安抚AUTO_INCREMENT 的必要(且充分)。

【讨论】:

  • 好吧,真正的查询有一个 WHERE 子句,例如 WHERE domain_id = 150 AND dtraff_time BETWEEN AND 。你的句子在范围条件下仍然有效吗?
  • 我更改了我的建议以适应您修改后的 WHERE。
猜你喜欢
  • 2012-06-11
  • 1970-01-01
  • 2016-06-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-02
  • 1970-01-01
相关资源
最近更新 更多