在MySQL 5.6中优化查询

时间:2013-10-15 15:50:20

标签: mysql optimization indexing

我有一张INNODB表levels

+--------------------+--------------+------+-----+---------+-------+
| Field              | Type         | Null | Key | Default | Extra |
+--------------------+--------------+------+-----+---------+-------+
| id                 | int(9)       | NO   | PRI | NULL    |       |
| level_name         | varchar(20)  | NO   |     | NULL    |       |
| user_id            | int(10)      | NO   |     | NULL    |       |
| user_name          | varchar(45)  | NO   |     | NULL    |       |
| rating             | decimal(5,4) | NO   |     | 0.0000  |       |
| votes              | int(5)       | NO   |     | 0       |       |
| plays              | int(5)       | NO   |     | 0       |       |
| date_published     | date         | NO   | MUL | NULL    |       |
| user_comment       | varchar(255) | NO   |     | NULL    |       |
| playable_character | int(2)       | NO   |     | 1       |       |
| is_featured        | tinyint(1)   | NO   | MUL | 0       |       |
+--------------------+--------------+------+-----+---------+-------+

有大约400万行。由于前端功能,我需要使用各种过滤器和排序来查询此表。它们位于playable_characterratingplaysdate_published上。可以过滤date_published以显示最后一天,一周,一个月或任何时间(过去3年)。还有分页。因此,根据用户的选择,查询可以看起来像,例如:

SELECT * FROM levels
WHERE playable_character = 0 AND
    date_published BETWEEN date_sub(now(), INTERVAL 3 YEAR) AND now()
ORDER BY date_published DESC
LIMIT 0, 1000;

SELECT * FROM levels
WHERE playable_character = 4 AND
    date_published BETWEEN date_sub(now(), INTERVAL 1 WEEK) AND now()
ORDER BY rating DESC
LIMIT 4000, 1000;

SELECT * FROM levels
WHERE playable_character = 5 AND
    date_published BETWEEN date_sub(now(), INTERVAL 1 MONTH) AND now()
ORDER BY plays DESC
LIMIT 1000, 1000;

我开始使用索引idx_date_char(date_published, playable_character),它在第一个示例查询中运行 great - 基本上是date_published排序的任何内容。使用EXPLAIN,我得到'使用索引条件',这很好。我想我理解为什么索引有效,因为WHERE和ORDER BY子句中存在相同的两个索引列。

我的问题在于按playsrating订购的查询。我知道我正在引入第三列,但对于我的生活,我无法得到一个效果很好的索引,尽管我只想到我能想到的每一个变化:每个顺序中所有三个或四个的复合索引,以及等等。也许查询可以用不同的方式编写?

我应该补充一点,ratingplays始终被查询为DESC。只有date_published可以是DESCASC

任何建议都非常感谢。 TIA。

3 个答案:

答案 0 :(得分:1)

where子句AND order by中使用的列应该是索引的一部分。

我会有一个索引
( playable_character, date_published DESC, rating DESC, plays DESC )

我将可播放角色置于FIRST的原因是您希望该ID为主要ID,然后是所有这些日期。评级和游戏是为了协助ORDER BY子句而进行的。)

想想这样的索引。如果您按Date_Published,然后Playable_Character订购,请考虑一个盒子的房间。每个方框都有一个日期..在给定日期的那个方框内,你可以按字符顺序排列它们。所以,你有3年的数据需要经过,你必须打开过去3年的所有盒子,找到你想要的角色。

现在,想想这样。每个方框都是按字符划分的,其中所有日期都是预先排序的。所以,你去一个盒子,打开它......移动到有问题的日期并从你想要的X-Y范围中获取记录。现在,您可以根据这些记录应用简单的订单。

答案 1 :(得分:1)

您似乎可以充分利用以这种方式为每个查询排序的数据:

  1. playable_character,date_published
  2. playable_character,date_published,rating
  3. playable_character,date_published,play
  4. 请记住,您需要在第一个查询中排序的数据恰好是第二个和第三个查询所需的数据的子集,因此我们可以摆脱它。

    另请注意,向索引添加DESCASC在语法上是正确的,但实际上并未更改任何内容,因为此功能目前不受支持(预计将来会支持此功能)这就是为什么它在那里)。所有索引都按升序存储。更多信息here

    所以这些是您应该创建的索引:

    ALTER TABLE levels ADD INDEX (playable_character, date_published, rating)
    ALTER TABLE levels ADD INDEX (playable_character, date_published, plays)
    

    那应该让那里的3个查询比Forrest Gump跑得更快。

答案 2 :(得分:1)

当您的查询包含范围谓词BETWEEN时,索引中的列顺序很重要。

  • 首先,包含相等谓词引用的一个或多个列。
  • 接下来,包含范围谓词引用的一列。
  • 范围谓词引用的列之后索引中的任何其他列不能用于其他范围谓词或用于排序。
  • 如果您没有范围谓词,则可以为排序顺序添加一列。

因此,您的第一个查询可以从(playable_character, date_published)上的索引中受益。排序应该是无操作,因为优化器只会按索引顺序获取行。

第二个和第三个查询必然会执行一个文件排序,因为您有一个范围谓词,然后您按其他列排序。如果您只有等式谓词,那么您将能够使用第三列来避免文件排序,但是当您有范围谓词时,这不起作用。

您可以期望的最好的条件是减少结果集的大小,以便它可以在内存中排序而不会执行太多sort merge passes。你可以通过增加sort_buffer_size来帮助它,但要注意不要增加太多,因为它是按线程分配的。

索引定义中的ASC / DESC关键字在MySQL中没有区别 见http://dev.mysql.com/doc/refman/5.6/en/create-index.html

  

这些关键字允许用于将来的扩展,以指定升序或降序索引值存储。目前,他们被解析但被忽略;索引值始终按升序存储。