使用索引优化Mysql查询

4

我的数据库模式包括以下两个表:

CREATE TABLE `categories` (
  `id` bigint(20) NOT NULL auto_increment,
  `title` varchar(128) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

并且

CREATE TABLE `articles` (
  `id` bigint(20) NOT NULL auto_increment,
  `title` varchar(512) NOT NULL,
  `body` longtext,
  `state` varchar(7) NOT NULL,
  `type` varchar(6) NOT NULL,
  `category` bigint(20) default NULL,
  `publishedAt` datetime default NULL,
  PRIMARY KEY  (`id`),
  KEY `FK_category_to_article_category` (`category`),
  CONSTRAINT `FK_category_to_article_category` FOREIGN KEY (`category`) REFERENCES `categories` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

对于文章表格,state列有"PUBLISHED"或"UNPUBLISHED"等值,type列有"NEWS"、"GOSSIP"和"OPINION"等值。

我的应用程序执行很多这样的查询:

select * from articles where state="PUBLISHED" and type in ("NEWS","GOSSIP") 
and category in (4) and publishedAt<=now() order by publishedAt desc;

我有大约10K篇文章,我正在尝试确定上述查询在使用默认的分类外键时是否表现更好,还是应该使用多列索引。

没有索引(使用“explain extended”):


+----+-------------+-------+------+---------------------------------+---------------------------------+---------+-------+------+-----------------------------+
| id | select_type | table | type | possible_keys                   | key                             | key_len | ref   | rows | Extra                       |
+----+-------------+-------+------+---------------------------------+---------------------------------+---------+-------+------+-----------------------------+
|  1 | SIMPLE      | this_ | ref  | FK_category_to_article_category | FK_category_to_article_category | 9       | const |  630 | Using where; Using filesort |
+----+-------------+-------+------+---------------------------------+---------------------------------+---------+-------+------+-----------------------------+

如果我创建了多列索引并再次解释(强制使用特定的索引):
create index I_s_t_c_p on articles (state, type, category, publishedAt);


+----+-------------+-------+-------+---------------+-----------+---------+------+------+------------------------------------------+
| id | select_type | table | type  | possible_keys | key       | key_len | ref  | rows | Extra                                    |
+----+-------------+-------+-------+---------------+-----------+---------+------+------+------------------------------------------+
|  1 | SIMPLE      | this_ | range | I_s_t_c_p     | I_s_t_c_p | 61      | NULL | 1216 | Using where; Using index; Using filesort |
+----+-------------+-------+-------+---------------+-----------+---------+------+------+------------------------------------------+

查询实际返回的行数为630。在我看来,多列索引应该比FK表现更好,因为所有索引列都被使用了,但是当使用索引时检查了约1200行的事实使我感到困惑。我知道这些数字只是估计值,但两个键之间的差异相当大;使用组合索引时,我们检查的行数是使用FK的两倍。

所以我的问题如下:

  1. 为什么使用多列索引会检查那么多行?
  2. 由于使用FK我们有一个连接类型“ref”,而使用组合索引我们有一个连接类型“range”,这是否意味着使用FK的查询比另一个查询更好/更快?
  3. 我应该将检查行数的估计作为决定索引是否良好/最优的标准吗?
  4. 在这种情况下,多列索引比FK更好吗?我应该基于什么来做出决策?

一些额外的信息:

  • 在没有强制查询上的索引的情况下,优化器选择了FK。当我对文章执行了analyze table操作后,选择了多列索引。
  • 我正在使用MySql 5.0.15
  • 索引信息

+----------+------------+---------------------------------+--------------+-------------+-------------+------------+
| Table    | Non_unique | Key_name                        | Seq_in_index | Column_name | Cardinality | Index_type |
+----------+------------+---------------------------------+--------------+-------------+-------------+------------+
| articles |          0 | PRIMARY                         |            1 | id          |       12561 | BTREE      |
| articles |          1 | FK_category_to_article_category |            1 | category    |          37 | BTREE      |
| articles |          1 | I_s_t_c_p                       |            1 | state       |           8 | BTREE      |
| articles |          1 | I_s_t_c_p                       |            2 | type        |          32 | BTREE      |
| articles |          1 | I_s_t_c_p                       |            3 | category    |         163 | BTREE      |
| articles |          1 | I_s_t_c_p                       |            4 | publishedAt |       12561 | BTREE      |
+----------+------------+---------------------------------+--------------+-------------+-------------+------------+

谢谢您的提前参与。


想法:如果“state”和“type”只能取有限的一组值,您可以将它们制作成ENUM或其他整数类型,这比VARCHAR更快速地进行比较。 - Kerrek SB
多键索引中的顺序很重要。请尝试使用(publishedAt,category,type,state)作为您的索引。 - bot403
正如我对Mchl的回复所述(请参见下方),publishedAt似乎是不必要的,但是以分类列为首的索引顺序与以状态为首的索引顺序相比,结果更差。这可能与文章数量有关吗?也许在约50K篇文章的情况下,一个以类别为首的索引可以展示更好的结果? - Argyro Kazaki
@Kerrek SB:我之前不知道mysql ENUM类型,所以感谢你的见解。但是我正在使用hibernate,似乎mysql ENUM的支持不太好(https://dev59.com/i3I95IYBdhLWcg3wxA8-)。另外从另一篇讨论中(https://dev59.com/rHRA5IYBdhLWcg3w9izq)得知,即使它可以提高我的select查询性能,也会减慢任何插入和更新操作的速度。 - Argyro Kazaki
1个回答

2

正如你所看到的,publishedAt 的索引与主键具有相同的基数。这并没有什么帮助。我建议尝试创建一个由列组成的复合索引,按照以下顺序排列:(category,type,state)。这样,索引的第一部分将是最具选择性的。


我也会完全这样做。根据常用的条件,将索引基于第一个元素返回最小条目。 - DRapp
关于 publishedAt 你是正确的。从索引中删除它后,立即将检查的行数减少了一半 - 与 FK 相同。此外,在约 20K 篇文章中测试,该索引的性能优于 FK - 这是合乎逻辑的假设。但是,您建议的索引顺序 - (category,type,state)- 似乎表现相同或更差,然而 (state, type, category, publishedAt) 索引表现更好。我理解“索引的第一部分应该是最具选择性的”,但我感到困惑,因为我看到的结果更差。这可能是因为 state 是单个等式约束条件吗? - Argyro Kazaki
1
不幸的是,索引选择性只是索引性能的线索。如果您的数据在类别之间分布不均,则其性能可能并不理想。 - Mchl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接