【问题标题】:MySQL partial indexes on varchar fields and group by optimizationMySQL 对 varchar 字段的部分索引和按优化分组
【发布时间】:2011-08-01 02:10:36
【问题描述】:

我在使用 MySQL 进行组查询时遇到了一些问题。

问题

查询不使用 varchar(255) 字段上的 10 个字符的部分索引来优化分组依据有什么原因?

详情

我的设置:

CREATE TABLE `sessions` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `ref_source` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `guid` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `initial_path` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `referrer_host` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `campaign` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_sessions_on_user_id` (`user_id`),
  KEY `index_sessions_on_referrer_host` (`referrer_host`(10)),
  KEY `index_sessions_on_initial_path` (`initial_path`(10)),
  KEY `index_sessions_on_campaign` (`campaign`(10))
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

这里没有显示一些列和索引,因为它们不会真正影响问题。

我想要做的是运行查询以查看所有引用主机以及来自每个主机的会话数。我没有一张大桌子,但它足够大,我全表扫描不好玩。我要运行的查询是:

SELECT COUNT(*) AS count_all, referrer_host AS referrer_host FROM `sessions` GROUP BY referrer_host;

解释给出:

+----+-------------+----------+------+---------------+------+---------+------+--------+---------------------------------+
| id | select_type | table    | type | possible_keys | key  | key_len | ref  | rows   | Extra                           |
+----+-------------+----------+------+---------------+------+---------+------+--------+---------------------------------+
|  1 | SIMPLE      | sessions | ALL  | NULL          | NULL | NULL    | NULL | 303049 | Using temporary; Using filesort |
+----+-------------+----------+------+---------------+------+---------+------+--------+---------------------------------+

我在referrer_host 上有一个部分索引,但它没有使用它。即使我尝试USE INDEXFORCE INDEX 也无济于事。解释是一样的,性能也是一样的。

如果我在 referrer_host 上添加完整索引,而不是 10 个字符的部分索引,那么一切都会更好,即使不是立即。 (350 毫秒对 10 秒)

我已经测试了大于该字段中最长条目的部分索引也无济于事。完整索引是唯一可行的方法。

【问题讨论】:

  • 你对“部分索引”的理解到底是什么?你能告诉我们 CREATE INDEX 语句吗?
  • @horse 我指的是仅包含字符串前 n 个字符的索引。索引创建在 table create 语句中。 KEY index_sessions_on_referrer_host (referrer_host(10))

标签: mysql group-by indexing


【解决方案1】:

使用完整索引,查询将查找扫描整个索引并返回每个唯一键指向的记录数。桌子没有动。

使用部分索引,引擎在查看记录之前不知道referrer_host 的值。它必须扫描整个表!

如果 referrer_host 的大多数值都小于 10 个字符,那么理论上,优化器可以使用索引,然后只检查超过 10 个字符的行。但是,因为这不是一个聚集索引,它必须进行许多非顺序磁盘读取才能找到这些记录。它最终可能会更慢,因为表扫描至少是顺序读取。优化器不会做出假设,而是进行扫描。

【讨论】:

  • 这很有意义。这里的关键部分是你仍然必须去表格以确定它们是否是相同的值。如果必须这样做,您不妨进行表扫描并将所有唯一值放入存储桶中。感谢您的洞察力!
【解决方案2】:

试试这个查询:

EXPLAIN SELECT COUNT(referrer_host) AS count_all, referrer_host  FROM `sessions` GROUP BY referrer_host;

现在,在 referrer_host = null 上,该组的计数将失败,但我不确定是否有其他方法可以解决这个问题。

【讨论】:

  • 这仍然会返回与问题中上述类似的解释。性能没有变化。如果我过滤掉空值referrer_host
  • 这不使用referrer_host上的索引键吗?它在我的本地主机中完成。
  • 我只是再次检查了一遍,它仍然运行得和没有索引一样快。我的盒子(MySQL 5.1.45)上的EXPLAIN SELECT COUNT(referrer_host) AS count_all, referrer_host AS referrer_host FROM sessions GROUP BY referrer_host 产生与问题状态完全相同的解释。
【解决方案3】:

您正在为表中的所有行在 referrer_host 上进行分组。由于您的索引不包括 referrer_host(它包含前 10 个字符!),它将扫描整个表。

我敢打赌,这会更快,虽然不太详细:

SELECT COUNT(*) AS count_all, substring(referrer_host,1,10) AS referrer_host FROM `sessions` GROUP BY referrer_host;

如果您需要完整的引荐来源网址,请将其编入索引。

【讨论】:

  • 我知道索引不会覆盖整个字符串,所以 MySQL 不能只从索引中提取它需要的值。我想我很惊讶它根本无法使用索引来帮助优化 group by,尤其是当我设置一个我知道涵盖所有字符串的部分索引时。
  • 如果你知道前 10 个字符涵盖了所有值,为什么要对它进行子串呢?或者考虑添加并使用仅包含前 10 个字符的第二列。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-21
  • 2018-06-18
  • 2021-01-27
  • 1970-01-01
  • 1970-01-01
  • 2010-11-29
相关资源
最近更新 更多