提问者:小点点

为什么ORDER BY和LIMIT1会使MySQL查询变慢这么多?


我有一个看起来非常简单的查询。 但是,如果我将ORDER BY和LIMIT结合起来,则性能会逐级下降。 我发现了几个关于MySQL在大表中性能有限的问题,但我不认为这是原因所在,因为查询工作没有任何限制。

以下是关于增加“复杂性”的问题

SELECT * FROM `mydata`.`mytable` WHERE ((token='XFRA1NMDU9XY') AND (section=210874));
/* Rows: 0  Time: 0,094 sec. */
SELECT * FROM `mydata`.`mytable` WHERE ((token='XFRA1NMDU9XY') AND (section=210874)) LIMIT 1;
/* Rows: 0  Time: 0,063 sec. */
SELECT * FROM `mydata`.`mytable` WHERE ((token='XFRA1NMDU9XY') AND (section=210874)) ORDER BY mailing;
/* Rows: 0  Time: 0,125 sec. */
SELECT * FROM `mydata`.`mytable` WHERE ((token='XFRA1NMDU9XY') AND (section=210874)) ORDER BY mailing LIMIT 1;
/* Rows: 0  Time: 45,500 sec. */

注意最后一行查询时间的显著增加。 这并不是异常值,而是被复制了好几次。 实际上,这个查询的查询时间只有3分钟甚至更长,而其他的一切都很正常。

下面是一些数据:

  • 该表有大约2.00.000个条目
  • 有大约5.000个条目,其中(section=210874)
  • 数据库在MySQL 8.0.20和Ubuntu 20.04上运行
  • 是一个InnoDB表,有(和其他)的索引,但没有令牌
  • 的索引

表结构如下:

CREATE TABLE `mytable` (
    `data` VARCHAR(32) NOT NULL COLLATE 'ascii_bin',
    `mailing` INT(10,0) NOT NULL,
    `token` VARCHAR(64) NULL DEFAULT NULL COLLATE 'ascii_bin',
    `section` INT(10,0) NOT NULL,
    `expiry` INT(10,0) NULL DEFAULT NULL,
    PRIMARY KEY (`data`) USING BTREE,
    INDEX `mailing_CS` (`mailing`) USING BTREE,
    INDEX `section_CS` (`section`) USING BTREE,
    CONSTRAINT `mailing_CS` FOREIGN KEY (`mailing`) REFERENCES `mydata`.`mailings` (`id`) ON UPDATE NO ACTION ON DELETE CASCADE,
    CONSTRAINT `section_CS` FOREIGN KEY (`section`) REFERENCES `mydata`.`sections` (`id`) ON UPDATE NO ACTION ON DELETE CASCADE
)
COLLATE='ascii_bin'
ENGINE=InnoDB
;

知道为什么ORDER BY和LIMIT 1的组合有这种效果吗? explain告诉我,前一个查询(section_cs)和最后一个查询(mailing_cs)使用了不同的键(索引)。

当然,我可以不使用限制1,因为每个令牌将没有行,一行或几行。 但我想了解根本的问题。


共3个答案

匿名用户

MySQL ORDER BY with LIMIT是对大型数据集进行排序的交互式应用程序中ORDER BY最常用的用法。

确保它使用索引。 让ORDER BY with LIMIT在不扫描和排序完整结果集的情况下执行是非常重要的,因此它使用索引很重要--在这种情况下,一旦生成所需数量的行,就会启动索引范围扫描并停止查询执行。

例如,如果我确实选择了*FROM sites ORDER BY date_created DESC LIMIT 10; 我会在(date_created)上使用index来快速获得结果集。

现在,如果我有一些类似SELECT*FROM sites WHERE category_id=5 ORDER BY date_created DESC LIMIT 10;

在这种情况下,按date_created索引也可以工作,但它可能不是最有效的--如果它是一个罕见的类别,可能会扫描表的很大一部分以找到10行。 因此在(category_id,date_created)上建立索引将是一个更好的主意。

索引可能会帮助你!!

匿名用户

我认为MySQL试图在最后一次查询中使用mailing_cs索引,而这个索引不是最佳的。

请尝试此查询:

SELECT * 
FROM `mydata`.`mytable` USE INDEX (section_CS) IGNORE INDEX(mailing_CS) 
WHERE (
    (token = 'XFRA1NMDU9XY') AND 
    (section = 210874)
) 
ORDER BY mailing 
LIMIT 1

此外,您也可以对此表使用复合索引(节,邮寄)。

匿名用户

WHERE ((token='XFRA1NMDU9XY')
  AND (section=210874))
ORDER BY mailing LIMIT 1;

需要以下组合索引中的任何一个:

INDEX(token, section, mailing)
INDEX(section, token, mailing)

应删除任何较短的索引(最左边的列匹配),以避免混淆。

至于你为什么有这些时间。。。

前两个--,其中a=1和b=2-将使用索引(a)索引(b),但必须扫描以检查另一个值。 因此,即使切换到index(a,b)(b,a)也会加快它们的速度。

对于第三个查询--,其中a=1和b=2按C-是一个谜题; 应该比4号还要长。

order by,没有合适的索引,需要以下内容:

  1. 收集所有潜在行(千?)
  2. 对它们进行排序。
  3. 最后剥离一行(限制1)

对于我的任何一个索引,第4个查询应该是毫秒级的。