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

Mos*_*ady 9 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;
Run Code Online (Sandbox Code Playgroud)

我想通过publishedon between x and y(在所有网站上显示1个月的记录)进行每个查询

在同一时间,我想publishedon在where子句中添加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))
Run Code Online (Sandbox Code Playgroud)

该查询是正常的,直到现在没有索引,但当尝试使用order by publishedon它变得太慢,所以我使用这个索引

CREATE INDEX `listings_pcs` ON listings(
    `publishedon` DESC,
    `published` ,
    `cat_id` ,
    `source_id`
)
Run Code Online (Sandbox Code Playgroud)

它工作,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
Run Code Online (Sandbox Code Playgroud)

这是解释 在此输入图像描述 这个查询太慢了因为 ORDER BY views DESC

然后我试图删除旧索引并添加此

CREATE INDEX `listings_pcs` ON listings(
    `publishedon` DESC,
    `published` ,
    `cat_id` ,
    `source_id`,
    `views` DESC
)
Run Code Online (Sandbox Code Playgroud)

它也太慢了

如果我只使用单个索引publishedon呢?如何在cat_id,source_id,views,publishedon上使用单个索引?

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

什么在做指数(cat_id,source_id,publishedon,published)?但在某些情况下,我只会使用source_id?

该表的最佳索引模式是什么

Gor*_*off 13

这个查询:

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));
Run Code Online (Sandbox Code Playgroud)

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

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

建议...没有那么好:

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

不建议使用last的原因是因为SQL(和MySQL)不保证子查询的结果排序.但是,因为MySQL实现了子查询,所以结果确实是有序的.我不喜欢使用无证的副作用,它可以在不同的版本之间变化.


smc*_*nes 4

关于为什么尽管您尝试了查询却没有变得更快的一个重要的一般说明是,DESCMySQL 目前不支持索引。请参阅此SO 线程及其来源

在这种情况下,您最大的问题在于记录的大小。如果引擎认为使用索引实际上不会更快,那么它就不会。

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

关于 SQL 的注释

首先,我想简单介绍一下 SQL 中的索引。虽然我不认为这可以解决您的困境,但这是您的主要问题,并且可以提供帮助。

它通常可以帮助我考虑在三个不同的存储桶中建立索引。绝对、也许永远。您的索引中当然没有任何位于never列中的内容,但有一些我会考虑“也许”索引。

Absolute:这是您的主键和任何外键。它也是您定期引用的任何键,以便从您拥有的大量数据中提取一小部分数据。

也许:这些列虽然您可能会定期引用它们,但它们本身并没有真正被引用。事实上,通过分析和使用@MachavityEXPLAIN在他的答案中建议的方式,您可能会发现,当这些列用于剥离字段时,无论如何都没有那么多字段了。对我来说,这个专栏中的一个例子就是专栏。请记住,每一项都会增加您的查询需要完成的工作。publishedINDEX

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

选项、选项、选项……

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

  1. 将 SQL 表拆分为两个或多个单独的表。

这是少数几次,尽管表中的列数很多,但我不会急于尝试将表拆分成更小的块。但是,如果您决定将其分成更小的块,我认为您的[action]edon, [action]edby_id, 和[action]ed可以很容易地放入另一个表中,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              |                |
+-----------+-------------+------+-----+-------------------+----------------+
Run Code Online (Sandbox Code Playgroud)

这样做的缺点是它不允许您确保只有一个没有TRIGGER. 好处是,当您按日期排序时,不必对尽可能多的列和尽可能多的索引进行排序。此外,它不仅允许您对 be 进行排序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
Run Code Online (Sandbox Code Playgroud)

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

actiondate如果您在和上放置复合键listings.id,这应该有助于提高速度。

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

  1. 创建月份字段

我使用这个漂亮的工具来确认我对你的问题的理解:你在这里按月份排序。您的示例专门针对 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
Run Code Online (Sandbox Code Playgroud)

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

由于您计划引用月份和日期,因此您可能需要针对月份和年份添加复合键,而不是分别为两者添加一个键,以增加收益。

注意:我想澄清一下,这不是“正确”的做事方式。它很方便,但非规范化。如果您想要正确的做事方式,您可以采用类似此链接的内容,但我认为这需要您认真重新考虑您的表格,而且我还没有尝试过这样的事情,因为缺乏必要,并且坦率地说,会,温习我的几何学。我认为这对于你想做的事情来说有点过分了。

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

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

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

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

  • DESC 是一个转移注意力的话题。将其更改为 ASC,查询仍然不会运行得更快。问题在于 IN 和范围。它们阻止对 ORDER BY 使用索引。 (3认同)