MariaDB 10.4 随机性能下降

MariaDB 10.4 random performance degradation

我的服务器有这样的参数:

已安装 5.5.5-10.4.12-MariaDB-1:10.4.12+maria~bionic。此屏幕截图显示了标准数据库负载:

所以我每秒大约有 400-500 次选择(主要来自不太大的 table,有 50 万条记录),每秒 100-190 次更新,大约 50-150 个同时连接。

我的问题是:有时,没有明显的原因,服务器有 2000-3000 个打开 connections/processes。根据 SHOW FULL PROCESSLIST,它们是标准的 SQL 请求,但具有 'Sending data' 状态和 400-500 秒的运行时间。当然这时候服务器卡顿了,无法正常运行。我说“没有明显的原因”是因为此时我没有看到网站上的用户数量或 activity 有任何增加。此外,重新启动 MariaDB 服务或完全重新启动服务器有助于摆脱这种情况,但并非总是如此:有时甚至在重新启动后我几乎立即得到相同的 2000-3000 个冻结进程。

有没有人遇到过类似的数据库行为?如果有任何想法,我将不胜感激。

更新:

  1. 我所有的 SELECT 只调用一个 table(约 500k 条记录,没有 JOIN and/or 子查询),而且大部分有 LIMIT 1, 所以数据量不是很大。

  2. 错误日志显示了很多这样的记录:2020-08-26 22:12:35 787380 [Warning] Aborted connection 787380 to db: ... (Got timeout reading communication packets)

  3. innodb_lock_wait_timeout 为 50(默认)

  4. 慢查询日志没有显示异常

  5. 我的 optimizer_switch 设置:index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_merge_sort_intersection=off,engine_condition_pushdown=off,index_condition_pushdown=on,derived_merge=on,derived_with_keys=on,firstmatch=on,loosescan=on,materialization=on,in_to_exists=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on,subquery_cache=on,mrr=off,mrr_cost_based=off,mrr_sort_keys=off,outer_join_with_cache=on,semijoin_with_cache=on,join_cache_incremental=on,join_cache_hashed=on,join_cache_bka=on,optimize_join_buffer_size=on,table_elimination=on,extended_keys=on,exists_to_in=on,orderby_uses_equalities=on,condition_pushdown_for_derived=on,split_materialized=on,condition_pushdown_for_subquery=on,rowid_filter=on,condition_pushdown_from_having=on

这听起来像是查询优化器随机运行的经典案例 brain-dead。这是一个长期存在的 heisenbug。

当您看到堆积的查询时,运行 SHOW EXPLAIN FOR thread_id 是其中一个堆积的 ID。查看查询计划是否为non-senical。如果是,请编辑查询 app-side 以包含索引提示以防止查询优化器出错。如果您无法更改查询,则必须使用 optimizer_switch 设置 fiddle,直到您识别并删除使优化器疯狂的特定选项。

error log 中有任何内容吗?

如果数据库冻结,很可能是磁盘问题:可能是磁盘已满,如果 mariadb 无法写入任何内容,则会冻结 1 分钟,如果临时 table使用复制算法填充它,或者对一个 table 进行修改;你在监控你的磁盘使用情况(不在图像中,你应该)?可能是磁盘 I/Os 全部被一个查询使用:那么所有查询仍然 运行,但速度很慢,所以是卡住了还是真的很慢?可能是锁问题?

由于查询 运行ning 很长时间(400-500s),它很可能不是锁:除非你改变它,锁等待超时更短(at least it is on innodb : 50s ).

如果你知道没有ALTER TABLE是运行,并且没有磁盘问题(you might want to check the inodes too),它仍然可能是一个锁:SHOW ENGINE INNODB STATUS\G检查.

你说做一个 SHOW FULL PROCESSLIST 只有标准的 SQL 请求,所以很可能没有 ALTER TABLE.

如果您的查询写得不好,临时 table 可能会填满您的磁盘,因此您需要 EXPLAIN 执行 SHOW FULL PROCESSLIST 时显示的查询来分析它,并重写/优化/限制此类查询结果集的大小,查找 using temporary(有时您也可以在磁盘上进行排序:using filesort)。 slow query log 会告诉你是否有查询正在使用磁盘(如果它们在你重启服务器时没有被杀死)。

如果您没有时间优化查询,并且如果它们很大 SELECT 会减慢您的整个数据库,为了向用户显示信息(报告),您可以终止耗时过长的查询一个脚本:这应该是最后的手段(你的脚本杀死查询花费的时间太长可以编写它们,这样你就可以稍后分析它们)。

临时 table 填满磁盘或使用所有 I/Os 是我看到数据库冻结并在重新启动后重新启动的唯一情况。对于数据库再次冻结的情况,可能是用户再次(又一次)执行相同的查询。

编辑

可能不是您的数据库有问题,而是您的 Web 应用程序:错误日志消息表明数据库正在终止某些连接。

查询的组合是发送数据中止连接对我来说很不寻常。通常,如果 Web 应用程序未关闭连接并且它们处于 Sleep 状态,则会发生中止连接。你可以检查 everything in this post :

  • 检查网络问题(防火墙)
  • 检查 Web 应用程序日志中的错误
  • 检查 max_allowed_packet 是否足够大(如果您的 SELECT 返回一行,应该没有问题)

如果有休眠查询,那么你没有正确关闭连接,然后你达到了 max_connection 限制,并且不会发生新的连接。仍然不清楚:数据库是否非常慢,或者根本没有发生任何事情? Web 服务器端发生了什么?

也可能是驱动程序(mariadb 客户端)使连接和查询处于 正在发送数据 状态,并且没有获取数据的末尾。如果它正在缓冲输出,并且在它实际可以之前被杀死(并且它也没有关闭连接),这可能会发生。它不符合 LIMIT 1 ,但这可以解释为什么在 Sending data 状态下存在中止连接和 SELECT 查询。您的 Web 应用程序使用什么语言?我可以想到 php 无缓冲查询 和 php 进程崩溃以重新创建这种情况,但这可能是另一种特定于语言的问题。反正这种情况很难得。

结果证明解决方案非常简单:在研究了 MariaDB 文档(尤其是这篇文章 https://mariadb.com/kb/en/thread-pool-in-mariadb/)后,我将以下内容添加到 my.cnf 并且问题消失了

thread_handling=pool-of-threads
thread_pool_size=48 
#48 is a number of CPUs

所以,我一直在努力解决“发送数据”查询,这些查询挂在一个非常高的写入配置文件系统上,我发现使用了上述解决方案以及降低脏页阈值并使用 FORCE INDEX (blah) 查询指令(由于 MariaDB 错误)。在可能的情况下,MariaDB 版本在 10.5.x 系列中,特别是现在的 10.5.13。解决方案如下:

# -------------------------------------------------------------------
# Flush as often as possible to increase performance on high
# update tables
# -------------------------------------------------------------------
innodb_max_dirty_pages_pct_lwm = 0.0001
innodb_max_dirty_pages_pct     = 0.0001

# -------------------------------------------------------------------
# Thread handling
# -------------------------------------------------------------------
thread_handling             = pool-of-threads
thread_pool_size            = 112

在解决方案之前,我很容易就有超过 50GB 的脏页,现在平均在 7-10GB 左右。由于系统完全是 NVMe,更频繁地刷新页面不会导致任何明显的延迟增加,但两者的结合会有所帮助。

注意: 如前所述,目前 MariaDB 存在一个查询优化器错误,优化器不会为 运行 查询选择正确的索引。有一些解决方法,但目前最好的方法是对较大的查询使用 FORCE INDEX (blah) 查询指令。这是采取的第三步。事实上,我刚刚看了 MariaDB 基金会的视频,他们建议在升级之前始终在新版本的 MariaDB 上测试你的查询。作为解决方法,我现在尽可能频繁地为 MariaDB 进行挑选。以下是错误报告:MariaDB Issue MDEV-25480 MariaDB Issue MDEV-25830

注意:这个系统在SMT模式下有56个核心,我根本没有看到SMT模式有什么好处。可能会在某个时候将其关闭。

注意: 虽然您可以在 UPDATEDELETE 查询中 运行 EXPLAIN,但您不能 FORCE INDEX (blah) 在其中任何一个上。所以,这个 bug 确实需要 MariaDB 基金会来解决。不过,如果能够将 FORCE INDEX (blah) 用于这些查询类型,那将是一个不错的补丁。