MySQL - 只需为索引字段添加 ORDER BY 即可为 52 条记录增加 5 分钟。从哪儿开始?

MySQL - just adding ORDER BY an indexed field adds 5 minutes for just 52 records. Where to start?

编辑 2:现在我们已经优化了数据库并在

中缩小了范围

编辑 1:有两种解决方案对我们有所帮助。一个在数据库级别(配置),一个在查询级别。我当然只能接受一个作为最佳答案,但是如果你有类似的问题,请看两个。

我们有一个多年来 运行 完美无缺的数据库。但是,现在,我们有一个我不明白的问题。是 mysql/InnoDB 配置问题吗?而且我们目前没有人负责系统维护(我是程序员)。

TitelDaggegevens 表有几个 Gig 大小,大约有 12,000,000 条记录,所以没什么特别的。

如果我们这样做:

SELECT * 
  FROM TitelDaggegevens 
 WHERE fondskosten IS NULL 
   AND (datum BETWEEN 20200401 AND 20200430)

它运行良好,在十分之几秒内。

结果:52条记录。

此外,如果我们添加 ORDER BY datum 或者如果我们按任何其他非索引字段排序:一切都很好,速度相同。

但是,如果我添加 ORDER BY id(id 是主键),对于相同的 52 条记录,查询突然需要 15 秒。

并且当我 ORDER BY 另一个索引字段时,查询时间增加到 4-6 分钟 。用于订购 52 条记录。在索引字段上。

不知道发生了什么。解释对我没有帮助。我optimized/recreated table,检查了它,然后重新启动了服务器。都无济于事。我绝对不是配置 MySQL 或 InnoDB 的专家,所以我不知道从哪里开始搜索。

我只是希望有人能认识到这一点,并能为我指明正确的方向。

SHOW TABLE STATUS WHERE Name = 'TitelDaggegevens' 给我:

我知道这是一个非常模糊的问题,但我无法更具体地确定它。我为慢速查询启用了日志记录,但 table slow_log 保持为空。我迷路了。

感谢您提供有关查找位置的任何想法。

这可能对了解它的人有帮助,但对我来说不是真的,phpmyadmins 'Advisor':

在评论和反应中要求 EXPLAIN 输出:

1) 没有 ORDER BYORDER BY datum (在 WHERE 中并且有一个索引):

2) 使用 ORDER BY 加上除 datum 以外的任何字段(索引或不索引,因此对于快速查询和慢速查询都是一样的)。

table结构:

CREATE TABLE `TitelDaggegevens` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `isbn` decimal(13,0) NOT NULL,
 `datum` date NOT NULL,
 `volgendeDatum` date DEFAULT NULL,
 `prijs` decimal(8,2) DEFAULT NULL,
 `prijsExclLaag` decimal(8,2) DEFAULT NULL,
 `prijsExclHoog` decimal(8,2) DEFAULT NULL,
 `stadiumDienstverlening` char(2) COLLATE utf8mb4_unicode_520_ci DEFAULT NULL,
 `stadiumLevenscyclus` char(1) COLLATE utf8mb4_unicode_520_ci DEFAULT NULL,
 `gewicht` double(7,3) DEFAULT NULL,
 `volume` double(7,3) DEFAULT NULL,
 `24uurs` tinyint(1) DEFAULT NULL,
 `UitgeverCode` varchar(4) COLLATE utf8mb4_unicode_520_ci DEFAULT NULL,
 `imprintId` int(11) DEFAULT NULL,
 `distributievormId` tinyint(4) DEFAULT NULL,
 `boeksoort` char(1) COLLATE utf8mb4_unicode_520_ci DEFAULT NULL,
 `publishingStatus` tinyint(4) DEFAULT NULL,
 `productAvailability` tinyint(4) DEFAULT NULL,
 `voorraadAlles` mediumint(8) unsigned DEFAULT NULL,
 `voorraadBeschikbaar` mediumint(8) unsigned DEFAULT NULL,
 `voorraadGeblokkeerdEigenaar` smallint(5) unsigned DEFAULT NULL,
 `voorraadGeblokkeerdCB` smallint(5) unsigned DEFAULT NULL,
 `voorraadGereserveerd` smallint(5) unsigned DEFAULT NULL,
 `fondskosten` enum('depot leverbaar','depot onleverbaar','POD','BOV','eBoek','geen') COLLATE utf8mb4_unicode_520_ci DEFAULT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `ISBN+datum` (`isbn`,`datum`) USING BTREE,
 KEY `UitgeverCode` (`UitgeverCode`),
 KEY `Imprint` (`imprintId`),
 KEY `VolgendeDatum` (`volgendeDatum`),
 KEY `Index op voorraad om maxima snel te vinden` (`isbn`,`voorraadAlles`) USING BTREE,
 KEY `fondskosten` (`fondskosten`),
 KEY `Datum+isbn+fondskosten` (`datum`,`isbn`,`fondskosten`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=16519430 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci 

看起来 ORDER BY 使用了 3 个不同的优化计划

  1. ORDER BY id - 额外:Using index condition; Using where; Using filesort。 MySQL 使用 filesort 解析 ORDER BY。但是行已经排序了。因此,需要 15 秒。
  2. ORDER BY Datum 或其他非索引字段 - 额外:Using index condition; Using where。 MySQL 使用 Datum 索引来解析 ORDER BY。需要几秒钟。
  3. ORDER BY index_field - 额外:Using index condition; Using where; Using filesort。 MySQL 使用 filesort 解析 ORDER BY。行未排序。需要几分钟。

这是我的建议。只有 EXPLAIN 才能知道发生了什么

Influencing ORDER BY Optimization

更新: 你能用每个 ORDER BY 个子句检查这个查询吗?

SELECT * 
  FROM TitelDaggegevens USE INDEX FOR ORDER BY (Datum)
 WHERE fondskosten IS NULL 
   AND (Datum BETWEEN 20200401 AND 20200430)

您也可以尝试增加 sort_buffer_size

If you see many Sort_merge_passes per second in SHOW GLOBAL STATUS output, you can consider increasing the sort_buffer_size value to speed up ORDER BY or GROUP BY operations that cannot be improved with query optimization or improved indexing. On Linux, there are thresholds of 256KB and 2MB where larger values may significantly slow down memory allocation, so you should consider staying below one of those values.

  1. 用这个来完全处理 WHERE

    INDEX(fondskosten, Datum)
    

注意:先是=,再是范围。

  1. 获取 *。注意:如果有大的 TEXTBLOB 列是您不需要的,请拼写出 SELECT 列表以便您可以避免它们。它们可能已存储 "off-record",因此需要更长的时间来获取。

  2. 可选ORDER BY。如果是在Datum上,那就没有额外的努力了。如果它在 any 其他列上,则将进行排序。但是52行的排序会非常快(毫秒)。

备注:

  • 如果您没有 fondskosten IS NULL 或您有其他测试,那么所有赌注都会被取消。我们必须重新设计最优综合指数。
  • USE/FORCE INDEX -- 将其用作最后的手段。
  • 在需要讨论查询时始终提供 SHOW CREATE TABLE
  • Advisor有一些好东西,但不知道"too big"是什么,比较没用。
  • 怀疑 所有其他讨论都没有意识到给定的Datum 范围有远远超过52 行。也就是说 fondskosten IS NULL 确实是问题和解决方案的一部分。

对于在类似情况下搜索调整的人来说,这些是专家对数据库所做的调整,大大加快了它的速度(请注意,这是针对具有 100 个表和许多非常复杂和大型查询的数据库,有时会加入超过15张表,但记录数不多。数据库只有37GB。

[mysqld]
innodb_buffer_pool_size=2G
innodb_buffer_pool_instances=4
innodb_flush_log_at_trx_commit=2

tmp_table_size=64M
max_heap_table_size=64M

join_buffer_size=4M
sort_buffer_size=8M

optimizer_search_depth=5

optimizer_search_depth 已减少,以最大限度地减少优化器复杂查询所需的时间。

重新启动服务器后,(定期)运行 所有作为 运行 宁此查询结果的查询:

SELECT CONCAT('OPTIMIZE TABLE `', TABLE_SCHEMA , '`.`', TABLE_NAME ,'`;') AS query
FROM INFORMATION_SCHEMA.TABLES
WHERE DATA_FREE/DATA_LENGTH > 2 AND DATA_LENGTH > 4*1024*1024

(如果您有大表,当服务器离线或使用率低时,第一个更好。它会重建并优化需要它的表。)

然后:

SELECT CONCAT('ANALYZE TABLE `', TABLE_SCHEMA , '`.`', TABLE_NAME ,'`;') AS query
FROM INFORMATION_SCHEMA.TABLES
WHERE DATA_FREE/DATA_LENGTH > 2 AND DATA_LENGTH > 1*1024*1024

(这第二个querie-series更轻量且侵权更少,但仍可能通过服务器重新计算查询策略来帮助加快某些查询。)