MySQL - 基本索引减慢简单聚合查询

时间:2017-10-27 15:22:00

标签: mysql indexing

我有一个简单的表格,用于存储流量数据:

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

BUT

通过要求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 INDEX:

+------+-------------+----------------+------+---------------+-----------+---------+-------+---------+-------+
| 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 INDEX

+------+-------------+----------------+------+---------------+------+---------+------+---------+-------------+
| 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
  3. 任何解释和建议?

    谢谢。

    没有优化器提示的结果(在@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)
    

2 个答案:

答案 0 :(得分:2)

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

您澄清了~4.1m行,domain_id = 150.因此,表中的每一行几乎与您的WHERE条件匹配。

想想书后面的索引。为什么它不包含像“the”或“and”这样的单词的条目?因为这些单词几乎出现在每一页上,并且索引它们是浪费时间,并使用索引查找这些常用单词的出现次数,翻到相应的页面,然后翻回索引以查找下一页发生在第2页,依此类推。

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

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

我的建议是:让优化器完成它的工作。它通常会根据查询逻辑频率做出关于是否使用索引的好决定你要搜索的具体价值。

重新评论:

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

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

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

这不一定非常频繁,但如果索引中的值分布发生显着变化(例如,如果进行主要的批量插入或批量删除),那么之后值得做分析表。

答案 1 :(得分:1)

你真的是两种方式吗?当忽略索引时,它可能运行得更快。 (比尔解释了原因。)

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

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

`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