【问题标题】:Finding optimal index for this MySQL query查找此 MySQL 查询的最佳索引
【发布时间】:2016-06-18 16:28:14
【问题描述】:

在 MySQL 慢查询日志中,我有以下查询:

SELECT * FROM `news_items`
WHERE `ctime` > 1465013901 AND `feed_id` IN (1, 2, 9) AND
`moderated` = '1' AND `visibility` = '1'
ORDER BY `views` DESC
LIMIT 5;

这是解释的结果:

+----+-------------+------------+-------+---------------------------------------------------------------------------------------+-------+---------+------+------+-------------+
| id | select_type | table      | type  | possible_keys                                                                         | key   | key_len | ref  | rows | Extra       |
+----+-------------+------------+-------+---------------------------------------------------------------------------------------+-------+---------+------+------+-------------+
|  1 | SIMPLE      | news_items | index | feed_id,ctime,ctime_2,feed_id_2,moderated,visibility,feed_id_3,cday_complex,feed_id_4 | views | 4       | NULL |    5 | Using where |
+----+-------------+------------+-------+---------------------------------------------------------------------------------------+-------+---------+------+------+-------------+
1 row in set (0.00 sec)

当我手动运行此查询时,大约需要 0.00 秒,但由于某种原因,它有时会出现在 MySQL 的慢速日志中,需要 1-5 秒。我相信当服务器处于高负载时会发生这种情况。

这是表结构:

CREATE TABLE IF NOT EXISTS `news_items` (
  `item_id` int(10) NOT NULL,
  `category_id` int(10) NOT NULL,
  `source_id` int(10) NOT NULL,
  `feed_id` int(10) NOT NULL,
  `title` varchar(255) CHARACTER SET utf8 NOT NULL,
  `announce` varchar(255) CHARACTER SET utf8 NOT NULL,
  `content` text CHARACTER SET utf8 NOT NULL,
  `hyperlink` varchar(255) CHARACTER SET utf8 NOT NULL,
  `ctime` varchar(11) CHARACTER SET utf8 NOT NULL,
  `cday` tinyint(2) NOT NULL,
  `img` varchar(100) CHARACTER SET utf8 NOT NULL,
  `video` text CHARACTER SET utf8 NOT NULL,
  `gallery` text CHARACTER SET utf8 NOT NULL,
  `comments` int(11) NOT NULL DEFAULT '0',
  `views` int(11) NOT NULL DEFAULT '0',
  `visibility` enum('1','0') CHARACTER SET utf8 NOT NULL DEFAULT '0',
  `pin` tinyint(1) NOT NULL,
  `pin_dttm` varchar(10) CHARACTER SET utf8 NOT NULL,
  `moderated` tinyint(1) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

名为“views”的索引仅包含 1 个字段——views。 我还有许多其他索引,包括(例如):

feed_id + views + visibility + moderated
moderated + visibility + feed_id + ctime
moderated + visibility + feed_id + views + ctime

我按上述顺序使用字段,因为这是 MySQL 开始使用它们的唯一原因。但是,我从来没有在 EXPLAIN 中得到“Using where; using index”。

关于如何让 EXPLAIN 向我展示“使用索引”的任何想法?

【问题讨论】:

  • 基本经验法则:决策上下文中使用的任何字段(例如,在wherejoinorder by 等中使用的字段)都应该被索引。您的索引似乎是多余的。如果一个字段出现在多个索引中,最好是它自己的单独索引。例如如果已审核更改,您将强制更改三个单独的索引,而不仅仅是一个。
  • 当我找到最好的索引时,我会删除大部分索引。这只是我为找到一个好的索引所做的一个例子。不管怎样,谢谢你的评论,我会记住的。

标签: mysql sql optimization query-optimization


【解决方案1】:

如果您已将存储引擎更改为 InnoDB 并创建了正确的复合索引,您可以试试这个。第一个查询仅获取前 5 行的 item_id。完成完整的 SELECT 后完成限制。所以最好在没有任何大数据的情况下做到这一点,然后只从 5 个问题中得到洞排

SELECT idata.* FROM (
  SELECT item_id FROM `news_items`
  WHERE `ctime` > 1465013901 AND `feed_id` IN (1, 2, 9) AND
  `moderated` = '1' AND `visibility` = '1'
  ORDER BY `views` DESC
  LIMIT 5 ) as i_ids
LEFT JOIN news_items AS idata ON idata.item_id = i_ids.item_id
ORDER BY `views` DESC;

【讨论】:

    【解决方案2】:

    如果您的表“还有许多其他索引”,为什么它们不显示在 SHOW CREATE TABLE 中?

    有两种方法

    WHERE `ctime` > 1465013901
      AND `feed_id` IN (1, 2, 9)
      AND `moderated` = '1'
      AND `visibility` = '1'
    ORDER BY `views` DESC
    

    可以使用索引:

    • INDEX(views) 并希望所需的 5 行(参见 LIMIT)早日出现。
    • INDEX(moderated, visibility, feed_id, ctime)

    最后一个“复合”索引从比较= constant 的两列(以任意顺序)开始,然后移动到IN,最后是一个“范围”(ctime > const)。旧版本无法通过IN;较新的版本将跳过IN 值并利用ctime 上的范围。 More discussion.

    ORDER BY 列包含在复合索引中的所有 WHERE 列之前是没有用的。但是,在您的情况下包含 views 将没有用,因为 ctime 上的“范围”。

    Bernd 提出的关于“惰性评估”的提示也会有所帮助。

    我同意 InnoDB 可能会更好。 Conversion tips.

    【讨论】:

      【解决方案3】:

      回答您的问题:“使用索引”意味着 MySQL 将仅使用索引来满足您的查询。为此,我们需要创建一个“覆盖”索引(“覆盖”查询的索引)=涵盖“where”和“order by/group by”以及“select”中的所有字段的索引但是,您正在做"select *" 这样就不实用了。

      MySQL 选择视图索引,因为您的查询限制为 5。这样做是因为 1)索引很小 2)在这种情况下它可以避免文件排序。

      我认为问题不在于索引,而在于 engine=MyISAM。 MyISAM 使用表级锁定,因此如果您更改 news_items,它将被锁定。我建议将表转换为 InnoDB。

      另一种可能是,如果表很大,索引(视图)可能不是最佳选择。

      如果您使用 Percona Server,您可以启用慢日志详细程度选项并查看慢查询的查询计划,如下所述:https://www.percona.com/doc/percona-server/5.5/diagnostics/slow_extended_55.html

      【讨论】:

      • 嗨亚历山大!感谢您的回答。所以你认为“views”是这个 MyISAM 表的最佳索引 ATM 吗?它有 30k 行,80Mbytes。我将尝试在 InnoDB 上进行一些测试。顺便说一句,我使用 Sphinx 在此表上进行全文搜索,因此我认为迁移不会是一个大问题。
      • Greg,同样从 MySQL 5.6 开始,您可以将 InnoDB 与全文索引一起使用。使用 Sphinx 也是一个好方法。对于 80Mb 表,我会说视图索引可能是这里最简单和最好的。我还建议清理其他索引,因为其中一些索引看起来像是重复的。在 MySQL 5.6+ 中,您还可以使用性能模式来查找所有未使用的索引。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-22
      • 2016-05-17
      • 2019-02-12
      • 2015-12-31
      • 2016-02-16
      相关资源
      最近更新 更多