MySQL 查询非常慢 - 偶尔
Posted
技术标签:
【中文标题】MySQL 查询非常慢 - 偶尔【英文标题】:MySQL queries very slow - occasionally 【发布时间】:2020-08-14 13:00:33 【问题描述】:我在 Ubuntu 18.4.4 LTS 上运行 MariaDB 10.2.31。 我经常遇到以下难题 - 尤其是在早上开始时,即我的 DEV 环境在夜间闲置时 - 而且在白天时常会遇到。
我有一张桌子(这也适用于其他桌子),大约。 15.000 行和(除其他外)VARCHAR 列上的索引,平均包含 5 到 10 个字符。
值得注意的是,包括这一列在内的大多数列都是 GENERATED ALWAYS AS (JSON_EXTRACT(....)) STORED
,因为我 99% 的数据来自 REST API 作为 JSON 编码的字符串(方便我将它们存储在一个列中并提取其他所有内容)。
当对该列 WHERE colname LIKE 'text%'
运行查询时,我发现查询结果持续时间为 0.006 秒。好的。当我查询EXPLAIN
ed 时,我可以看到正在使用该索引。
然而,正如我所提到的,当我早上开始时,这需要更长的时间(今天早上 14 秒)。我知道查询缓存,我在关闭查询缓存的情况下尝试了这个(通过SET GLOBAL query_cache_type=OFF
和RESET QUERY CACHE
)。在这种情况下,我得到大约一致的时间。 0.3 秒 - 符合预期。
那么,您建议我应该研究什么?我的数据库在睡觉吗?有这种事吗?
【问题讨论】:
这可能是dba.stackexchange.com 的问题,但要从某个地方开始:您的数据库服务器是否在一夜之间做某事/是否有人在访问它(例如备份、其余 api,...)?这包括该服务器上的所有数据库。 我确实有一个守护进程正在运行,它不断地获取和插入提到的 JSON 数据(并进行其他清理工作)。这意味着即使没有人使用前端,数据库也应始终处于使用状态。 【参考方案1】:可能会发生两件事:
1) 冷缓存(隔夜备份、mysqld 重启或大型处理作业会导致此特定索引和表数据从内存中逐出)。
2) 表上的统计信息过时,查询计划器会感到困惑,直到您对表运行一些查询并且统计信息被刷新。您可以使用 ANALYZE TABLE table_name 强制更新。
3) 查询规划器 heisenbug。在 MySQL 5.7 及更高版本中非常常见,以前在 MariaDB 上从未见过,所以这不太可能。
您可以通过在配置中启用以下内容来深入了解:
log_output='FILE'
log_slow_queries=1
log_slow_verbosity='query_plan,explain'
long_query_time=1
然后在您看到缓慢发生后立即查看缓慢日志中的内容。如果记录的解释计划在慢速和快速情况下看起来都一样,那么你有一个冷缓存问题。如果它们不同,则您有一个表统计信息问题,您需要在通宵任务结束时 cron ANALYZE TABLE
读取/写入该表很多。如果这没有帮助,作为最后的手段,使用FORCE INDEX (index_name)
将索引提示硬编码到您的查询中。
【讨论】:
heisenbug 还在发生,我在 10.0 早期就看到了。写了innodb_stats_traditional=OFF 以改进统计数据收集。在恢复索引强制之前,请查看持久统计信息恕我直言。 在我停用我的守护进程后,我似乎得到了更好的结果,所以我相信它的许多读/写操作是罪魁祸首。然而,这只是在很短的时间之后。此外,我的守护进程似乎占用了大量 RAM(这是我的 TODO 列表中的一件事)。这会从缓冲池中推出页面还是固定大小? 缓冲池被保留,内存压力不会将其推出(但由于OOM情况可能会崩溃)。我建议您在大页面中分配内存(my.cnf 中的 large-pages=1,您必须使用 sysctl 单独配置大页面分配,可能需要重新启动以释放足够的连续内存)。大页面的结果是分配给它们的内存对其他进程是不可见的,并且它们是不可交换的,因此无法将缓冲池推出交换 - 如果内存压力很大,可能会发生另一件事。 【参考方案2】:使用log_slow_verbosity=query_plan,explain
和long_query_time
启用您的slow query log 足以捕获结果。看看它是否偶尔使用不同的(或没有)索引。
在您开始新的一天之前,请查看SHOW GLOBAL STATUS LIKE "innodb_buffer_pool%"
,并在您查询后再次查看这些值。查看此状态输出中有多少缓冲池读取与读取请求,以查看是否全部都从磁盘中取出。
正如@Solarflare 所提到的,备份和夜间活动可能会清除缓存数据的 innodb 缓冲池并将坏数据恢复到磁盘以使其再次变慢。作为夜间活动的一部分,您可以设置 innodb_buffer_pool_dump_now=1 以在脚本活动之前保存热页面,并设置 innodb_buffer_pool_load_now=1 以恢复它。
【讨论】:
谢谢,我会看看我能从 STATUS 输出中收集到什么。看来我必须深入寻找原因。 ;) 同时学习一些有价值的东西。快乐学习。期待在 dba.stackexchange.com/ 上看到后续问题。 最明确! :D 谢谢。我会更新我的进度。【参考方案3】:大喊并感谢大家提供宝贵的见解! 从你们提供的所有提示中,我想我开始更好地理解问题并开始缩小范围:
我发现的第一件事是我的默认 innodb_buffer_pool_size
为 134 MB。对于我正在处理的数据的种类和数量,这非常低 - 所以我能够增加它。
很有帮助的帖子:https://dba.stackexchange.com/a/27341
来自文档:https://dev.mysql.com/doc/refman/8.0/en/innodb-buffer-pool-resize.html
现在我已经将它增加到接近 2GB 并且能够监控它的使用情况和一般的 RAM 使用情况(cli: cat /proc/meminfo)我意识到我的 4GB RAM 实际上处于低端。我几乎看不到任何未使用的开销(缓冲区使用率仍为 99%,可用 RAM 约为 100MB)。
接下来我将开始优化我的守护程序的 RAM 使用情况,看看这会导致什么 - 但这不会完全释放足够的 RAM。
@danblack 提到了innodb_buffer_pool_dump_now
和innodb_buffer_pool_load_now
。这是一种有趣的方法,可以在守护程序访问数据库时使用,因为我希望将守护程序的缓冲区使用与前端的(显然这是不可能的!)分开。我会进一步研究,但由于我的守护进程一直在运行(不仅在晚上),这可能不可行。
@Gordan Bobic 提到使用ANALYZE TABLE tableName
“刷新”DBtables。我发现这非常快,并在每次进行广泛的读/写后将其合并到守护程序中。这会将守护程序的运行时间增加几秒钟,但这根本不是问题。而且我认为我不会出错:)
所以,最后我相信我的问题是多种因素的组合:缓冲区太小、RAM 太小、该环境的读/写操作太多(驱逐缓冲索引等)。 此外,我还必须了解有关内存分配等的更多信息并更好地优化它(large-pages=1 等)。
【讨论】:
以上是关于MySQL 查询非常慢 - 偶尔的主要内容,如果未能解决你的问题,请参考以下文章