MySQL 查询花费的时间比首次运行时应该花费的时间长(但仅在选择某个字节阈值时)


MySQL query takes longer than it should on first run (but only when selecting a certain byte threshold)

我第一次在页面加载上运行此查询时,它总是需要大约 100 毫秒,即使它应该大约需要 1 毫秒。我通过MySQL控制台和PhpMyAdmin运行了完全相同的查询,而且它总是很快。

我在 PHP 中对其进行了计时,如下所示:

$t = microtime(true);
mysql_query($sql, $this->id);
die((microtime(true)-$t)*1000);

如果我第二次运行完全相同的查询(例如通过复制中间行),第二次几乎立即运行。

查询如下所示:

SELECT `user_id`, `login`, `first_name`, `last_name`, `name`, `email`, ... FROM `users` WHERE `user_id`='1000' LIMIT 1

通过实验,我发现如果我减少我选择的字段数量,它就会突然运行得很快。当我添加第 1 列时,它从 1 毫秒跳到 100 毫秒。第 26 列是什么似乎并不重要,即使它是像"1"这样的常数,它也会突然变慢。

可能导致此问题的原因是什么,我该如何解决?


架构:

CREATE TABLE `user` (
    `f1` int(11) unsigned NOT NULL DEFAULT '0',
    `f2` varchar(50) NOT NULL DEFAULT '',
    `f3` varchar(32) NOT NULL DEFAULT '',
    `f4` varchar(50) DEFAULT NULL,
    `f5` varchar(50) DEFAULT NULL,
    `f6` varchar(100) NOT NULL DEFAULT 'Anonymous',
    `f7` varchar(100) DEFAULT NULL,
    `f8` varchar(100) NOT NULL DEFAULT 'Anonymous',
    `f9` tinyint(1) NOT NULL DEFAULT '0',
    `f10` tinyint(1) unsigned NOT NULL DEFAULT '0',
    `f11` varchar(250) DEFAULT NULL,
    `f12` int(10) unsigned NOT NULL DEFAULT '0',
    `f13` varchar(250) NOT NULL DEFAULT '',
    `f14` int(10) unsigned NOT NULL DEFAULT '0',
    `f15` varchar(100) DEFAULT NULL,
    `f16` varchar(25) DEFAULT NULL,
    `f17` varchar(25) DEFAULT NULL,
    `f18` varchar(25) DEFAULT NULL,
    `f19` varchar(200) DEFAULT NULL,
    `f20` varchar(50) DEFAULT NULL,
    `f21` varchar(50) DEFAULT 'BC',
    `f22` varchar(50) DEFAULT 'Canada',
    `f23` varchar(20) DEFAULT NULL,
    `f24` bigint(20) unsigned NOT NULL DEFAULT '0',
    `f25` bigint(20) unsigned NOT NULL DEFAULT '0',
    `f26` int(5) unsigned zerofill NOT NULL DEFAULT '00000',
    `f27` tinyint(1) NOT NULL DEFAULT '0',
    `f28` tinyint(1) unsigned NOT NULL DEFAULT '0',
    `f29` tinyint(1) NOT NULL DEFAULT '0',
    `f30` tinyint(1) unsigned NOT NULL DEFAULT '0',
    `f31` varchar(50) DEFAULT NULL,
    `f32` varchar(100) NOT NULL DEFAULT '',
    `f33` bigint(20) unsigned NOT NULL DEFAULT '0',
    `f34` bigint(20) unsigned NOT NULL DEFAULT '0',
    `f35` varchar(250) DEFAULT NULL,
    `f36` text NOT NULL,
    `f37` tinyint(1) unsigned NOT NULL DEFAULT '0',
    `f38` bigint(20) NOT NULL DEFAULT '0',
    `f39` bigint(20) NOT NULL DEFAULT '0',
    `f40` tinyint(1) unsigned NOT NULL DEFAULT '0',
    `f41` tinyint(1) unsigned NOT NULL DEFAULT '0',
    PRIMARY KEY (`f1`),
    UNIQUE KEY `f2` (`f2`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

我更改了字段名称以保护无辜者。


更新:我可能误会了魔术列限制。我认为这毕竟是一个字节限制...我尝试了所有数字,除了我实际需要的少数字段:

SELECT `f1`, `f2`, `f3`, `f4`, `f5`, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55 FROM `wx_user` WHERE `user_id`='1000' LIMIT 1

这在 1 毫秒内运行。如果我在末尾添加"56",则需要 ~100 毫秒。


我做了更多的测试:

mysql_query("SELECT 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' FROM `user` WHERE `f1`='1000' LIMIT 1");

在 1200 到 1300 a 之间,它从 1ms 跳到 100ms。

我可以使用更多的列重复同样的事情,但数据(整数)更小。

这向我暗示了两件事:

  1. 它与发送的字节数有关
  2. 添加额外列的开销非常大,因为选择大约 50 个整数与选择单个 1300 个字符的字段具有相同的效果。

现在我想起来,这个数字与约翰建议的数字非常接近。1300 个字符 + 开销可能等于 John 提到的 1500 MTU 限制。

这是MySQL设置还是操作系统设置?我可以尝试的东西?

MySQl 会缓存您的查询,因此第二次执行它时,它会从缓存加载并且会更快。

还要确保在表上有正确的索引,因为这可以大大加快速度。

正如 John 所说,同一查询的第二次运行从缓存中检索其结果,因此它几乎是即时的。

话虽如此,在某些情况下,查询的"运行"时间可能比添加第 X 列时更长。几种可能性:

  • 第 X 列很大(如 CHAR(65000)),传输它需要时间,
  • 所有 (X-1) 列都是已在 MySQL 内存中的索引的一部分,但对第 X 列的引用会强制 MySQL 从驱动器读取行,
  • 同时查询锁定引用的表或导致 CPU/硬盘/内存负载,
  • 第 X 列是 TEXT/BLOB,必须进行额外的磁盘查找才能从外部文件中检索列值(BLOB 和 TEXT 值存储在表数据文件之外),
  • 第 X 列向必须从 MySQL 服务器发送到应用程序的行数据添加足够的字节,必须使用两个或多个网络数据包来传输整行。如果你真的,真的,我的意思是**真的**必须从查询中挤出每一毫秒,确保查询结果适合一个MTU帧(通常为1500字节) - 使用嗅探器确认它,
  • 其他情况水平很低,正常人不会在现实世界中看到它们。

您还可以使用内置的MySQL查询分析来找出哪个查询执行阶段需要花费大量时间。跑:

SET profiling = 1;
[..run Your queries, each one is numbered..]
SHOW PROFILE FOR QUERY n; -- SHOW PROFILE FOR QUERY 1;
SET profiling = 0;

您应该非常了解花费额外时间的地方。在此处阅读有关查询分析的更多信息:MySQL 查询分析

好吧,MySQL 确实有一个缓存,它可以加快速度......

但不止于此。问题不在您的查询中。我遇到了同样的事情,我无法调试它的原因正是缓存:我无法再次产生相同的问题。不仅仅是MySQL,它也是缓存东西的文件系统,因此完全不可能重现最初的缓慢 - 而且我不确定它是否会第二次发生,也许背后的原因导致它不会再次发生。 尝试重新启动MySQL,尝试刷新缓存,但没有任何内容可以重现第一次运行。

还有更神秘的!有一种解决方法没有意义:如果您在查询之前执行 CHECK TABLE(并确保它只是第一次!),查询本身将以正常速度运行。 不,与已经缓存时的速度不同;这是一个正常但仍然是第一次的速度。 正如您所期望的那样,再次调用它会立即产生结果。

现在每个人都点头说"当然,文件系统在执行 CHECK 时会缓存表文件,以便下次运行得更快"。 但事实并非如此。 如果我尝试使用任何其他程序将 MYD 文件读入内存(我做了一个简单的"复制"),它也不能解决问题,即使我也复制索引。 然而,CHECK似乎做了一些真正有帮助的事情。

结论: - 这不是一个简单的"慢查询"问题,这是黑暗巫术。 显然,此解决方法不适用于生产环境。 我之所以提到它,是因为它显示了两件重要的事情:

  1. 区别不仅在于缓存。 还有更多
  2. 在开发期间,可以应用简单的修复方法。

希望这对那里的一些人来说有所帮助 - 花了我几天时间:)