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

时间:2015-10-03 13:11:34

标签: php mysql sql indexing query-optimization

我有这张桌子(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子句publishedon中添加published, cat_id , source_id

有些事情是这样的:

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

这是解释 enter image description here 由于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_idsource_idpublishedonpublished中制作索引怎么样?但在某些情况下,我只会使用source_id?

该表的最佳索引架构是什么

4 个答案:

答案 0 :(得分:12)

此查询:

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以外的所有人都没有使用=

因为您的性能问题是排序,这表明正在返回大量行。通常,在将索引用于WHERE之前,索引用于满足ORDER BY子句。这使得这很难优化。

建议。 。 。没有那么好:

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

不建议使用last的原因是因为SQL(和MySQL)不保证从子查询中排序结果。但是,因为MySQL实现了子查询,所以结果确实是有序的。我不喜欢使用无证副作用,这些副作用可能会因版本而异。

答案 1 :(得分:2)

如果我是你,我至少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

答案 2 :(得分:2)

关于为什么你的查询在没有尝试的情况下获得任何更快速度的一个重要的一般性说明是MySQL目前不支持DESC索引。请参阅此SO threadthe source

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

你有几个选择,而且所有选项都非常不错,可能会帮助你看到明显的改善。

关于SQL的说明

首先,我想快速了解一下SQL中的索引。虽然我不认为它是解决你的困境的解决方案,但这是你的主要问题,并且可以提供帮助。

通常可以帮助我考虑在三个不同的存储桶中建立索引。 绝对 可能从不。你肯定没有在从不列的索引中有任何内容,但有一些我会考虑" 可能&# 34;索引。

绝对 :这是您的主键和任何外键。它也是您定期参考的任何密钥,用于从您拥有的大量数据中提取一小组数据。

也许:这些列虽然您可以定期引用它们,但它们本身并未真正引用。事实上,通过分析并在其答案中使用EXPLAIN作为@Machavity建议,您可能会发现,当这些列用于删除字段时,无论如何都不会有这么多字段。对我而言,这一列中的一个列的例子就是published列。请注意,每个INDEX都会增加您的查询需要完成的工作。

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

选项,选项,选项......

有许多选项需要考虑,每种方案都有一些缺点。最终,我会根据具体情况考虑每一项,因为我不认为其中任何一项都是银弹。理想情况下,您可以针对当前设置测试一些不同的解决方案,并使用一个不错的科学测试来查看哪一个运行速度最快。

  1. 将SQL表拆分为两个或多个单独的表。
  2. 这是为数不多的几次,尽管你的桌子中有多列,但我不会急于尝试将你的桌子拆分成更小的块。但是,如果您决定将其拆分为较小的块,我认为您的[action]edon[action]edby_id[action]ed可以很容易地放入另一个表中,{{1} }:

    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 actiondate上放置一个复合键,这有助于提高速度。

    正如我所说的,我现在不认为这是最适合您的解决方案,因为我不相信它会为您提供最大程度的优化。这引出了我的下一个建议:

    1. 创建月份字段
    2. 我使用this nifty tool来确认我认为我对您的问题的理解:您在这里按月分类。你的例子是9月1日到9月30日之间的具体情况。

      因此,另一个选项是将整数函数拆分为listings.idmonthday字段。您仍然可以获得时间戳,但时间戳并不适合搜索。即使是一个简单的查询也可以运行year,您就可以自己查看。

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

      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 ,您应该会看到大幅改进。

      由于您计划引用一个月和一天,您可能希望针对月份和年份添加复合键,而不是单独添加一个键,以增加收益。

      注意 :我想清楚,这不是"正确的"做事的方式。它很方便,但是非规范化。如果你想要正确的做事方式,你会适应this link之类的东西,但我认为这需要你认真地重新考虑你的桌子,而我还没有尝试过这样的事情,因为缺乏需要,而且,坦率地说,将会刷新我的几何形状。我认为你尝试做的事情有点过分。

      1. 在其他地方进行重排>
      2. 这对我来说很难接受,因为我喜欢做" SQL"尽可能的方式,但这并不总是最好的解决方案。例如,重型计算最好使用您的编程语言完成,让SQL处理关系。

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

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

答案 3 :(得分:1)

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

您向我们展示了一个包含以下过滤条件的查询

  1. published上的单值相等。
  2. publishedon上的范围匹配。
  3. cat_id
  4. 上设置匹配
  5. source_id上设置匹配。
  6. 订购视图。
  7. 由于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值逐个获取表的完整行。 (在我提到的索引的帮助下,这个只获取id值的查询应该非常快。)内部循环解决方案会很难看,但如果你的文本列真的很大(每个mediumtext列都可以容纳16MiB)这可能是你最好的选择。

    TL;博士。创建提到的索引。如果可能的话,摆脱SELECT *,给出你需要的列列表。尝试延迟连接查询。如果它还不够好,请尝试嵌套查询。