【问题标题】:Slow MySql query with order by limit with index带有索引的按限制排序的慢速 MySql 查询
【发布时间】:2015-11-06 12:09:04
【问题描述】:

我有一个由 Entity Framework 生成的查询,如下所示:

SELECT
`Extent1`.`Id`, 
`Extent1`.`Name`, 
`Extent1`.`ExpireAfterUTC`, 
`Extent1`.`FileId`, 
`Extent1`.`FileHash`, 
`Extent1`.`PasswordHash`, 
`Extent1`.`Size`, 
`Extent1`.`TimeStamp`, 
`Extent1`.`TimeStampOffset`
FROM `files` AS `Extent1` INNER JOIN `containers` AS `Extent2` ON `Extent1`.`ContainerId` = `Extent2`.`Id`
 ORDER BY 
`Extent1`.`Id` ASC LIMIT 0,10

它运行缓慢。 我有关于 files.Id (PK)、files.ContainerId(FK)、containers.Id(PK) 的索引,我不明白为什么 mysql 在返回所需记录之前似乎要进行完整排序,即使已经有Id 列上的索引。

此外,这些数据显示在支持过滤器、排序和分页的网格中,并且非常需要很好地使用索引。

以下是表定义:

CREATE TABLE `files` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `FileId` varchar(100) NOT NULL,
  `ContainerId` int(11) NOT NULL,
  `ContainerGuid` binary(16) NOT NULL,
  `Guid` binary(16) NOT NULL,
  `Name` varchar(1000) NOT NULL,
  `ExpireAfterUTC` datetime DEFAULT NULL,
  `PasswordHash` binary(32) DEFAULT NULL,
  `FileHash` tinyblob NOT NULL,
  `Size` bigint(20) NOT NULL,
  `TimeStamp` double NOT NULL,
  `TimeStampOffset` double NOT NULL,
  `FilePostId` int(11) NOT NULL,
  `FilePostGuid` binary(16) NOT NULL,
  `AttributeId` int(11) NOT NULL,
  PRIMARY KEY (`Id`),
  UNIQUE KEY `FileId_UNIQUE` (`FileId`),
  KEY `Files_ContainerId_FK` (`ContainerId`),
  KEY `Files_AttributeId_FK` (`AttributeId`),
  KEY `Files_FileId_index` (`FileId`),
  KEY `Files_FilePostId_index` (`FilePostId`),
  KEY `Files_Guid_index` (`Guid`),
  CONSTRAINT `Files_AttributeId_FK` FOREIGN KEY (`AttributeId`) REFERENCES `attributes` (`Id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `Files_ContainerId_FK` FOREIGN KEY (`ContainerId`) REFERENCES `containers` (`Id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `Files_FilePostsId_FK` FOREIGN KEY (`FilePostId`) REFERENCES `fileposts` (`Id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=977942 DEFAULT CHARSET=utf8;


CREATE TABLE `containers` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `Name` varchar(255) NOT NULL,
  `Guid` binary(16) NOT NULL,
  `AesKey` binary(32) NOT NULL,
  `FileCount` int(10) unsigned NOT NULL DEFAULT '0',
  `Size` bigint(20) unsigned NOT NULL,
  PRIMARY KEY (`Id`),
  KEY `Containers_Guid_index` (`Guid`),
  KEY `Containers_Name_index` (`Name`)
) ENGINE=InnoDB AUTO_INCREMENT=76 DEFAULT CHARSET=utf8;

您会注意到 files 表中还有一些其他关系,我省略了这些关系只是为了简化查询而不影响观察到的行为。

这也是 EXPLAIN EXTENDED 的输出:

+----+-------------+---------+-------+----------------------+-----------------------+---------+----------------------------------+-------+----------+----------------------------------------------+
| id | select_type |  table  | type  |    possible_keys     |          key          | key_len |               ref                | rows  | filtered |                    Extra                     |
+----+-------------+---------+-------+----------------------+-----------------------+---------+----------------------------------+-------+----------+----------------------------------------------+
|  1 | SIMPLE      | Extent2 | index | PRIMARY              | Containers_Guid_index |      16 | NULL                             |     9 | 100.00   | Using index; Using temporary; Using filesort |
|  1 | SIMPLE      | Extent1 | ref   | Files_ContainerId_FK | Files_ContainerId_FK  |       4 | netachmentgeneraltest.Extent2.Id | 73850 | 100.00   |                                              |
+----+-------------+---------+-------+----------------------+-----------------------+---------+----------------------------------+-------+----------+----------------------------------------------+

文件表有大约 900000 条记录(并且还在计数),容器有 9 条。 仅当存在 ORDER BY 时才会出现此问题。 此外,我无法在修改查询方面做太多事情,因为它是由实体框架生成的。为了简化它,我尽可能多地使用 LINQ 查询(起初它有一些执行速度更慢的可怕子查询)。

查询提示(如强制索引)在这里也不是解决方案,因为 EF 不支持此类功能。

我主要希望找到一些数据库级别的优化来做。

对于那些没有发现标签的人来说,有问题的数据库是 MySql。

【问题讨论】:

  • 你能不能尝试一个 STRAIGHT_JOIN 来强制它加入表的顺序。希望允许它使用 files.id 上的索引进行排序。
  • @Kickstart 确实,straight_join 有效!结果是立竿见影的。但是如何在实体框架中使用这个呢?有什么线索吗?
  • 怕没有经验。然而,在每个表上运行 ANALYZE TABLE .... 可能是值得的。可能是细节已过时,因此选择了低效的加入顺序
  • @Kickstart 我在两张桌子上都运行了 ANALYZE TABLE,但仍然没有乐趣。
  • 如果容器只有9行,可以去掉KEY Files_ContainerId_FK (ContainerId)吗?

标签: mysql entity-framework


【解决方案1】:

MySQL 每个表只使用一个索引。现在,它更喜欢使用外键索引,这样连接是有效的,但这意味着排序没有使用索引。

尝试在ContainerId, filedID上创建复合索引

【讨论】:

  • 我已经在 id, containerid 上尝试了一个复合索引,但是 mysql 只有在我强制的情况下才会使用它。结果符合预期,但我无法使用实体框架中的查询提示。你确定你的列顺序正确吗?通过使用该索引,即使我强迫它,也没有影响。
【解决方案2】:

这基本上是您的查询:

SELECT e1.*
FROM `files` e1 INNER JOIN
     `containers` e2
     ON e1.`ContainerId` = e2.`Id`
ORDER BY e1.`Id` ASC
LIMIT 0, 10;

您可以尝试files(id, ContainerId) 上的索引。这可能会启发 MySQL 使用复合索引,专注于 order by

如果查询被表述为:

SELECT e1.*
FROM `files` e1
WHERE EXISTS (SELECT 1 FROM containers e2 WHERE e1.`ContainerId` = e2.`Id`)
ORDER BY e1.`Id` ASC
LIMIT 0, 10;

有一种方法可以使用索引。但是,它依赖于 MySQL 中没有记录工作的东西(尽管它在实践中确实如此)。以下将按顺序读取数据,但会产生实现子查询的开销——但不是排序:

SELECT e1.*
FROM (SELECT e1.*
      FROM files e1
      ORDER BY e1.id ASC
     ) e1
WHERE EXISTS (SELECT 1 FROM containers e2 WHERE e1.`ContainerId` = e2.`Id`)
LIMIT 0, 10;

【讨论】:

  • 我已经在 id, containerid 上尝试了一个复合索引,但是 mysql 只有在我强制的情况下才会使用它。结果符合预期,但我无法使用实体框架中的查询提示。
猜你喜欢
  • 2017-03-29
  • 2013-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-29
  • 1970-01-01
  • 1970-01-01
  • 2014-03-24
相关资源
最近更新 更多