MySQL 索引 - 根据此 table 和查询的最佳实践是什么

MySQL indexes - what are the best practices according to this table and queries

我有这个 table(500,000 行)

CREATE TABLE IF NOT EXISTS `listings` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `type` tinyint(1) NOT NULL DEFAULT '1',
  `hash` char(32) NOT NULL,
  `source_id` int(10) unsigned NOT NULL,
  `link` varchar(255) NOT NULL,
  `short_link` varchar(255) NOT NULL,
  `cat_id` mediumint(5) NOT NULL,
  `title` mediumtext NOT NULL,
  `description` mediumtext,
  `content` mediumtext,
  `images` mediumtext,
  `videos` mediumtext,
  `views` int(10) unsigned NOT NULL,
  `comments` int(11) DEFAULT '0',
  `comments_update` int(11) NOT NULL DEFAULT '0',
  `editor_id` int(11) NOT NULL DEFAULT '0',
  `auther_name` varchar(255) DEFAULT NULL,
  `createdby_id` int(10) NOT NULL,
  `createdon` int(20) NOT NULL,
  `editedby_id` int(10) NOT NULL,
  `editedon` int(20) NOT NULL,
  `deleted` tinyint(1) NOT NULL,
  `deletedon` int(20) NOT NULL,
  `deletedby_id` int(10) NOT NULL,
  `deletedfor` varchar(255) NOT NULL,
  `published` tinyint(1) NOT NULL DEFAULT '1',
  `publishedon` int(11) unsigned NOT NULL,
  `publishedby_id` int(10) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `hash` (`hash`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8;

我正在考虑通过 publishedon between x and y 进行每个查询(在所有站点中仅显示 1 个月的记录)

同时,我想在where子句published, cat_id , source_id

中加上publishedon

像这样的事情:

SELECT * FROM listings 
WHERE (publishedon BETWEEN 1441105258 AND 1443614458) 
  AND (published = 1) 
  AND (cat_id in(1,2,3,4,5)) 
  AND (source_id  in(1,2,3,4,5))

到目前为止,该查询在没有索引的情况下还可以,而且速度很快,但是当尝试使用 order by publishedon 时,它变得太慢了,所以我使用了这个索引

CREATE INDEX `listings_pcs` ON listings(
    `publishedon` DESC,
    `published` ,
    `cat_id` ,
    `source_id`
)

它起作用了,order by publishedon 变快了,现在我想 order by views 像这样

SELECT * FROM listings 
WHERE (publishedon BETWEEN 1441105258 AND 1443614458) 
  AND (published = 1) 
  AND (cat_id in(1,2,3,4,5)) 
  AND (source_id  in(1,2,3,4,5)) 
ORDER BY views DESC

这是解释 此查询太慢,因为 ORDER BY views DESC

然后我尝试删除旧索引并添加这个

CREATE INDEX `listings_pcs` ON listings(
    `publishedon` DESC,
    `published` ,
    `cat_id` ,
    `source_id`,
    `views` DESC
)

它也太慢了

如果我在 publishedon 上只使用单个索引呢? 在 cat_id、source_id、views、publishedon 上使用单一索引怎么样?

如果我发现其他索引方法依赖于任何其他列,我可以在一个月内更改查询依赖项,如 publishedon

在 (cat_id, source_id, publishedon, published) 中创建索引怎么样?但在某些情况下,我只会使用 source_id?

什么是最好的索引架构 table

这个查询:

SELECT *
FROM listings
WHERE (publishedon BETWEEN 1441105258 AND 1443614458) AND
      (published = 1) AND
      (cat_id in (1,2,3,4,5)) AND
      (source_id in (1,2,3,4,5));

仅用索引很难优化。最好的索引是以 published 开头然后有其他列的索引——不清楚它们的顺序应该是什么。原因是因为除 published 以外的所有人都没有使用 =.

因为您的性能问题与排序有关,这表明有很多行正在 returned。通常,索引用于满足 WHERE 子句,然后才能将索引用于 ORDER BY。这使得它很难优化。

建议。 . . None 真棒:

  • 如果您打算按月访问数据,那么您可以考虑按月对数据进行分区。这将使没有 ORDER BY 的查询更快,但对 ORDER BY.
  • 没有帮助
  • 在索引中 published 之后尝试各种列顺序。您可能会找到最具选择性的列。但是,这再次加快了排序前的查询速度。
  • 考虑构建查询的方式,以便在 WHERE 子句中具有更多相等条件或 return 较小的数据集。
  • (不太推荐)在 published 和排序列上放置一个索引。然后使用子查询来获取数据。将不等式条件(IN 等)放在外部查询中。子查询将使用索引进行排序,然后过滤结果。

不推荐最后一个的原因是因为SQL(和MySQL)不保证子查询结果的顺序。但是,因为 MySQL 实现了子查询,所以结果确实是有序的。我不喜欢使用未记录的副作用,它会因版本而异。

如果我是你,我至少会 INDEX 单独列出有问题的字段。您正在构建多列索引,但很明显您也在提取大量不同的记录。将列单独编入索引不会有什么坏处。

您应该做的是使用 EXPLAIN,它可以让您深入了解 MySQL 是如何提取数据的。它可能进一步指出是什么导致您的查询变慢。

EXPLAIN SELECT * FROM listings 
WHERE (publishedon BETWEEN 1441105258 AND 1443614458) 
  AND (published = 1) 
  AND (cat_id in(1,2,3,4,5)) 
  AND (source_id  in(1,2,3,4,5)) 
ORDER BY views DESC

您的 table 的行数很大(所有这些 mediumtext 列),因此排序 SELECT * 会产生大量开销。这是您的模式设计的简单现实。 SELECT * 通常被认为对性能有害。如果您可以枚举所需的列,并且可以省略一些大的列,您将获得更好的性能。

您向我们展示了具有以下过滤条件的查询

  1. published 上的单值相等。
  2. publishedon 上的范围匹配。
  3. cat_id
  4. 上设置匹配
  5. source_id 上设置匹配。
  6. 按浏览量排序。

由于 MySQL 索引在 MyISAM 上的工作方式,以下复合覆盖索引可能会很好地为您服务。除非你尝试,否则很难确定。

CREATE INDEX listings_x_pub_date_cover ON listings( 
     published, publishedon, cat_id, source_id, views, id )

为了满足您的查询,MySQL 引擎将在 published 的适当值处随机访问索引,然后在 publishedon 范围的开头处。然后它将扫描其他两个过滤条件的索引过滤。最后,它排序并使用 id 值查找通过过滤器的每一行。试一试。

如果性能不够好,请尝试这种所谓的 deferred join 操作。

SELECT a.*
  FROM listings a
  JOIN ( SELECT id, views
           FROM listings
          WHERE published = 1
            AND publishedon BETWEEN 1441105258
                                AND 1443614458
            AND cat_id IN (1,2,3,4,5)
            AND source_id IN (1,2,3,4,5)
          ORDER BY views DESC
       ) b ON a.id = b.id
 ORDER BY b.views DESC

这仅使用 id 和 views 列就完成了繁重的排序工作,而无需重新排列所有那些庞大的文本列。它可能有帮助也可能没有帮助,因为必须在外部查询中重复排序。当您的查询中有 ORDER BY ... LIMIT n 模式时,这种事情肯定会有所帮助,但您没有。

最后,考虑到这些行的大小,您可以通过从 php 程序执行此内部查询来获得最佳性能:

         SELECT id
           FROM listings
          WHERE published = 1
            AND publishedon BETWEEN 1441105258
                                AND 1443614458
            AND cat_id IN (1,2,3,4,5)
            AND source_id IN (1,2,3,4,5)
          ORDER BY views DESC

然后在内部循环中使用这些 id 值逐一获取 table 的完整行。 (在我提到的索引的帮助下,这个只获取 id 值的查询应该非常快。)内部循环解决方案会很难看,但是如果你的文本列真的很大(每个 mediumtext 列可以容纳16MiB) 这可能是您最好的选择。

tl;博士。创建提到的索引。如果可能的话,摆脱 SELECT * ,而是提供您需要的列列表。尝试延迟连接查询。如果仍然不够好,请尝试嵌套查询。

一个重要的一般说明,关于为什么尽管您进行了尝试,您的查询却没有变得更快,是 DESC 当前不支持索引 DESC。请参阅它的来源SO thread, and the source

在这种情况下,您最大的问题是记录的庞大规模。如果引擎认为使用索引不会真的更快,那么它就不会。

您有几个选项,实际上所有选项都相当不错,可能会帮助您看到显着的改进。

关于 SQL

的注释

首先,我想快速说明一下 SQL 中的索引。虽然我认为这不能解决您的问题,但这是您的主要问题,并且可以提供帮助。

它通常可以帮助我考虑在三个不同的桶中建立索引。 绝对可能永远不会never 列中的索引中肯定没有任何内容,但我会考虑一些“maybe”索引。

absolutely:这是你的主键和任何外键。它也是您将定期参考的任何键,以从您拥有的海量数据中提取一小部分数据。

可能:这些栏目虽然您可能经常引用它们,但它们本身并没有真正被引用。事实上,通过分析和使用 在他的回答中推荐的 EXPLAIN,您可能会发现,当这些列用于剥离字段时,反正没有那么多字段。 published 列是对我来说绝对会出现在这一堆中的一个例子。请记住,每个 INDEX 都会增加您的查询需要完成的工作。

另外: 当您定期搜索基于两个不同列的数据时,复合键是一个不错的选择。稍后会详细介绍。

选项,选项,选项...

有许多选项可供考虑,每个选项都有一些缺点。最终,我会根据具体情况考虑每一项,因为我认为这些都不是灵丹妙药。理想情况下,您将根据当前设置测试几种不同的解决方案,然后使用良好的科学测试查看哪个运行速度最快。

  1. 将您的 SQL table 分成两个或多个独立的 table。

尽管您的 table 中有很多列,但我不会急于尝试将您的 table 分成更小的块,这是少数几次之一。但是,如果您决定将其拆分为更小的块,我认为您的 [action]edon[action]edby_id[action]ed 可以很容易地放入另一个 table、actions:

+-----------+-------------+------+-----+-------------------+----------------+
| Field     | Type        | Null | Key | Default           | Extra          |
+-----------+-------------+------+-----+-------------------+----------------+
| id        | int(11)     | NO   | PRI | NULL              | auto_increment |
| action_id | int(11)     | NO   |     | NULL              |                |
| action    | varchar(45) | NO   |     | NULL              |                |
| date      | datetime    | NO   |     | CURRENT_TIMESTAMP |                |
| user_id   | int(11)     | NO   |     | NULL              |                |
+-----------+-------------+------+-----+-------------------+----------------+

缺点是它不允许您确保只有一个创建日期没有 TRIGGER。好处是当您按日期排序时不必使用尽可能多的索引对尽可能多的列进行排序。此外,它不仅允许您根据 created 进行排序,还可以根据您的所有其他操作进行排序。

编辑:根据要求,这是一个示例排序查询

SELECT * FROM listings 
INNER JOIN actions ON actions.listing_id = listings.id
WHERE (actions.action = 'published') 
  AND (listings.published = 1) 
  AND (listings.cat_id in(1,2,3,4,5)) 
  AND (listings.source_id  in(1,2,3,4,5)) 
  AND (actions.actiondate between 1441105258 AND 1443614458)
ORDER BY listings.views DESC

理论上,它应该减少排序所依据的行数,因为它只提取相关数据。我没有像您这样的数据集,所以我现在无法对其进行测试!

如果你在actiondatelistings.id上放一个复合键,这应该有助于提高速度。

正如我所说,我认为这不是目前最适合您的解决方案,因为我不相信它会给您带来最大的优化。这引出了我的下一个建议:

  1. 创建月份字段

我用 this nifty tool 来确认我对你的问题的理解:你在这里按月排序。您的示例专门针对 9 月 1 日至 9 月 30 日(含)之间的日期。

因此,另一种选择是将整数函数拆分为 monthdayyear 字段。您仍然可以拥有时间戳,但时间戳对于搜索来说并不是那么好。 运行 一个 EXPLAIN 即使是一个简单的查询,你也会亲眼看到。

这样,您可以只索引月份和年份字段并执行如下查询:

SELECT * FROM listings 
WHERE (publishedmonth = 9)
  AND (publishedyear = 2015) 
  AND (published = 1) 
  AND (cat_id in(1,2,3,4,5)) 
  AND (source_id  in(1,2,3,4,5)) 
ORDER BY views DESC

在前面打一个 EXPLAIN,您应该会看到巨大的改进。

因为您打算引用一个月和一天,您可能希望添加针对月份和年份的复合键,而不是分别针对两者的键,以增加收益。

注意:我想明确一点,这不是“正确”的做事方式。这很方便,但非规范化。如果你想要正确的做事方式,你会适应像 this link 这样的东西,但我认为这需要你认真地重新考虑你的 table,而且我没有尝试过这样的事情,缺乏需要,坦率地说,将复习我的几何学。我认为这对于您要尝试做的事情来说有点矫枉过正。

  1. 在其他地方进行繁重的排序

这对我来说很难接受,因为我喜欢尽可能以“SQL”方式做事,但这并不总是最好的解决方案。例如,繁重的计算最好使用您的编程语言来完成,留下 SQL 来处理关系。

Digg 的前 CTO 使用 PHP 而不是 MySQL 进行排序,并获得了 4,000% performance increase。当然,您可能不会扩展到这个级别,因此除非您自己进行测试,否则性能权衡不会很明确。尽管如此,这个概念还是合理的:数据库是瓶颈,相比之下计算机内存便宜得要命。

毫无疑问,可以做更多的调整。这些都有一个缺点,需要一些投资。最好的答案是测试其中的两个或更多,看看哪一个可以帮助您获得最大的改进。