简介
对于将数据库纳入其技术栈的网站和应用程序,数据库性能会很大程度上影响用户体验。慢速查询会延迟数据检索、页面渲染以及与数据层交互的任何其他操作。由于这种潜在的重大影响,了解如何识别和解决这些问题非常重要。
在本文中,我们将讨论在 MySQL 数据库中识别性能不佳的查询的各种方法。这将为优化这些查询和提高其性能奠定基础。
检查活动查询和进程
最直接的检查方式之一是查看 MySQL 的 进程列表,以了解 MySQL 的当前运行状态。
显示完整进程列表
要显示 MySQL 正在执行的当前操作的处理线程,请键入以下内容:
SHOW FULL PROCESSLIST;
+----+-----------------+-----------+------+---------+---------+------------------------+-----------------------+| Id | User | Host | db | Command | Time | State | Info |+----+-----------------+-----------+------+---------+---------+------------------------+-----------------------+| 5 | event_scheduler | localhost | NULL | Daemon | 1834441 | Waiting on empty queue | NULL || 50 | root | localhost | NULL | Query | 0 | init | show full processlist |+----+-----------------+-----------+------+---------+---------+------------------------+-----------------------+2 rows in set (0.00 sec)
上面的输出显示了一个空闲服务器,其中只有我们自己的查询和一个长时间运行的事件监听器。活动服务器将显示更多进程,其中一些可能是长时间运行的。如果没有 `FULL` 修饰符,此命令将仅显示前 100 个进程,这可能会截断你的结果,具体取决于你的服务器活动。
需要关注的一些重要部分是 `Time` 和 `State` 列。`Time` 列计算线程处于所述 `State` 的秒数。如果你发现 `Time` 值不符合给定操作的预期,则可能需要进一步调查。
检查存储引擎状态
另一个检查的地方是实际存储引擎的状态。
你可以键入以下内容来查找与给定表关联的 存储引擎:
SHOW CREATE TABLE <database>.<table>\G
例如,要显示 `mysql.time_zone` 表使用的存储引擎,请键入以下内容:
SHOW CREATE TABLE mysql.time_zone`\G
*************************** 1. row ***************************Table: time_zoneCreate Table: CREATE TABLE `time_zone` (`Time_zone_id` int unsigned NOT NULL AUTO_INCREMENT,`Use_leap_seconds` enum('Y','N') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'N',PRIMARY KEY (`Time_zone_id`)) /*!50100 TABLESPACE `mysql` */ ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 STATS_PERSISTENT=0 ROW_FORMAT=DYNAMIC COMMENT='Time zones'1 row in set (0.01 sec)
`ENGINE=InnoDB` 表示该表使用的是 InnoDB 存储引擎。这是大多数配置中的默认存储引擎,因此你可能需要检查其状态。
你可以键入以下内容来显示 InnoDB 引擎的状态:
SHOW ENGINE INNODB STATUS\G
*************************** 1. row ***************************Type: InnoDBName:Status:=====================================2022-02-26 11:55:52 139992513984256 INNODB MONITOR OUTPUT=====================================Per second averages calculated from the last 16 seconds-----------------BACKGROUND THREAD-----------------srv_master_thread loops: 1 srv_active, 0 srv_shutdown, 1835091 srv_idlesrv_master_thread log flush and writes: 0----------SEMAPHORES----------OS WAIT ARRAY INFO: reservation count 555OS WAIT ARRAY INFO: signal count 457RW-shared spins 0, rounds 0, OS waits 0RW-excl spins 0, rounds 0, OS waits 0RW-sx spins 0, rounds 0, OS waits 0Spin rounds per wait: 0.00 RW-shared, 0.00 RW-excl, 0.00 RW-sx------------TRANSACTIONS------------Trx id counter 8778Purge done for trx's n:o < 8713 undo n:o < 0 state: running but idleHistory list length 0LIST OF TRANSACTIONS FOR EACH SESSION:---TRANSACTION 421467955133656, not started0 lock struct(s), heap size 1128, 0 row lock(s)---TRANSACTION 421467955132848, not started0 lock struct(s), heap size 1128, 0 row lock(s)---TRANSACTION 421467955132040, not started0 lock struct(s), heap size 1128, 0 row lock(s)--------FILE I/O--------I/O thread 0 state: waiting for completed aio requests (insert buffer thread)I/O thread 1 state: waiting for completed aio requests (log thread)I/O thread 2 state: waiting for completed aio requests (read thread)I/O thread 3 state: waiting for completed aio requests (read thread)I/O thread 4 state: waiting for completed aio requests (read thread)I/O thread 5 state: waiting for completed aio requests (read thread)I/O thread 6 state: waiting for completed aio requests (write thread)I/O thread 7 state: waiting for completed aio requests (write thread)I/O thread 8 state: waiting for completed aio requests (write thread)I/O thread 9 state: waiting for completed aio requests (write thread)Pending normal aio reads: [0, 0, 0, 0] , aio writes: [0, 0, 0, 0] ,ibuf aio reads:, log i/o's:, sync i/o's:Pending flushes (fsync) log: 0; buffer pool: 01779 OS file reads, 3384 OS file writes, 1870 OS fsyncs0.00 reads/s, 0 avg bytes/read, 0.00 writes/s, 0.00 fsyncs/s-------------------------------------INSERT BUFFER AND ADAPTIVE HASH INDEX-------------------------------------Ibuf: size 1, free list len 0, seg size 2, 0 mergesmerged operations:insert 0, delete mark 0, delete 0discarded operations:insert 0, delete mark 0, delete 0Hash table size 34679, node heap has 2 buffer(s)Hash table size 34679, node heap has 9 buffer(s)Hash table size 34679, node heap has 9 buffer(s)Hash table size 34679, node heap has 15 buffer(s)Hash table size 34679, node heap has 7 buffer(s)Hash table size 34679, node heap has 3 buffer(s)Hash table size 34679, node heap has 4 buffer(s)Hash table size 34679, node heap has 6 buffer(s)0.00 hash searches/s, 0.00 non-hash searches/s---LOG---Log sequence number 38540718Log buffer assigned up to 38540718Log buffer completed up to 38540718Log written up to 38540718Log flushed up to 38540718Added dirty pages up to 38540718Pages flushed up to 38540718Last checkpoint at 385407182197 log i/o's done, 0.00 log i/o's/second----------------------BUFFER POOL AND MEMORY----------------------Total large memory allocated 0Dictionary memory allocated 473011Buffer pool size 8192Free buffers 6241Database pages 1896Old database pages 719Modified db pages 0Pending reads 0Pending writes: LRU 0, flush list 0, single page 0Pages made young 8, not young 00.00 youngs/s, 0.00 non-youngs/sPages read 1754, created 142, written 9260.00 reads/s, 0.00 creates/s, 0.00 writes/sNo buffer pool page gets since the last printoutPages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/sLRU len: 1896, unzip_LRU len: 0I/O sum[0]:cur[0], unzip sum[0]:cur[0]--------------ROW OPERATIONS--------------0 queries inside InnoDB, 0 queries in queue0 read views open inside InnoDBProcess ID=59529, Main thread ID=139992560166656 , state=sleepingNumber of rows inserted 0, updated 0, deleted 0, read 00.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 0.00 reads/sNumber of system rows inserted 0, updated 317, deleted 0, read 61280.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 0.00 reads/s----------------------------END OF INNODB MONITOR OUTPUT============================1 row in set (0.00 sec)
输出将包含大量有关引擎正在使用的资源、正在执行的进程等的详细信息。你可以使用它来了解执行过程中是否存在瓶颈,或者正在争用的进程数量是否导致性能问题。
启用慢速查询日志记录
获取有关长时间运行或执行缓慢的查询的更多信息的一种方法是使用慢速查询日志记录。慢速查询日志记录告诉 MySQL 在查询超过某个执行阈值时记录查询。它可以非常有效地定位运行不佳的特定查询,而无需实时捕获进程列表。
检查 MySQL 是否正在记录慢速查询
你应该做的第一件事是验证慢速查询日志记录的当前状态。如果慢速查询日志记录已启用,则无需执行任何操作。
你可以键入以下内容来检查慢速查询日志记录是否已启用:
SHOW GLOBAL VARIABLES LIKE 'slow_query_log%';
+---------------------+------------------------------------+| Variable_name | Value |+---------------------+------------------------------------+| slow_query_log | OFF || slow_query_log_file | /var/lib/mysql/mysqlutils-slow.log |+---------------------+------------------------------------+2 rows in set (0.01 sec)
上面的输出表示当前未记录慢速查询,因为该功能已关闭。
如果慢速查询日志记录已打开,则你的输出将如下所示:
+---------------------+-------------------------------+| Variable_name | Value |+---------------------+-------------------------------+| slow_query_log | ON || slow_query_log_file | /var/log/mysql/mysql-slow.log |+---------------------+-------------------------------+2 rows in set (0.02 sec)
现在你知道了当前状态,可以根据需要进行更改。
配置 MySQL 记录慢查询
在我们继续之前,需要注意的是,尽管慢查询日志非常有用,但它可能会对性能产生额外的影响。MySQL 必须执行额外的操作来计时每个查询并记录结果到日志中。这会影响性能,并可能意外地填满硬盘空间。
可能并不总是需要记录慢查询。相反,在积极调查问题时启用该功能,并在完成后禁用它。
考虑到这一点,您可以通过修改 MySQL 服务器的配置文件来配置慢查询日志。您也可以交互式地修改这些值,但在配置文件中设置良好的默认值会使以后交互式调整变得更容易。
打开 MySQL 的配置文件。在大多数基于 Debian Linux 的系统上,配置文件位于 /etc/mysql/mysql.conf.d/mysqld.conf
sudo vim /etc/mysql/mysql.conf.d/mysqld.conf
我们需要修改或添加以下设置
变量 | 设置 | 描述 |
---|---|---|
slow_query_log | ON | 切换是否启用慢查询日志。 |
slow_query_log_file | /var/log/mysql/mysql-slow.log | 记录慢查询的日志文件。 |
long_query_time | (以秒为单位) | 查询必须超过的阈值(以秒为单位),才能被视为“慢”查询。 |
min_examined_row_limit | (行数) | 查询必须考虑的行数,才能成为慢查询候选者。 |
log_slow_admin_statements | ON | 切换是否也将管理命令记录在日志中。 |
log_queries_not_using_indexes | ON | 切换是否记录没有使用索引的查询。 |
log_slow_extra | ON | 对于 MySQL 服务器版本 8.0.14 或更高版本,这将切换是否记录有关查询的附加信息。 |
log_slow_replica_statements | ON | 对于 MySQL 服务器版本 8.0.26 或更高版本,这将切换是否记录在副本上执行的慢速语句。这仅适用于 binlog_format 设置为 STATEMENT 或 MIXED 的语句。 |
log_slow_slave_statements | ON | 对于 MySQL 服务器版本 8.0.25 或更早版本,这将切换是否记录在副本上执行的慢速语句。这仅适用于 binlog_format 设置为 STATEMENT 或 MIXED 的语句。 |
例如,如果我们想打开所有可选日志记录,并记录任何至少检查 100 行且执行时间至少为 2 秒的语句,我们可以使用以下设置
; enable MySQL to log slow queriesslow_query_log = ON; query log locationslow_query_log_file = /var/log/mysql/mysql-slow.log; amount of seconds that a query must surpass to be loggedlong_query_time = 2; minimum number of rows affected for a query to be loggedmin_examined_row_limit = 100; include administrative commands in the logslog_slow_admin_statements = ON; also log queries that are not using indexeslog_queries_not_using_indexes = ON; for MySQL servers version 8.0.14 or later, we can; get extra information by setting thislog_slow_extra = ON; Also log slow statements that have executed on; the replica; This only will log statements if `binlog_format` is; set to `STATEMENT` or `MIXED` and the statement is; logged in statement format; RE: https://dev.mysqlserver.cn/doc/refman/8.0/en/replication-options-replica.html#sysvar_log_slow_replica_statements; This option name depends on your MySQL version:; versions >= 8.0.26: log_slow_replica_statements; versions < 8.0.26: log_slow_slave_statements; log_slow_slave_statements = ONlog_slow_replica_statements = ON
保存并关闭文件后,您可以通过键入以下命令来验证您的配置更改
sudo mysqld --validate-config
如果没有返回错误,则您的 MySQL 服务器配置文件语法有效。您可以通过键入以下命令重新启动 MySQL 服务器进程
sudo systemctl restart mysql
您可以通过重新运行原始的发现查询来验证慢查询是否已启用
SHOW GLOBAL VARIABLES LIKE 'slow_query_log%';
在配置好慢查询后,您可以在 MySQL 本身中根据需要启用和禁用它。调整值的语法如下
SET GLOBAL slow_query_log = 'OFF';
使用 mysqldumpslow
分析慢查询日志
在获得慢查询日志记录生成的日志后,您可以通过几种不同的方式来分析它,以找出问题的确切位置。
分析日志最简单的方法是使用 mysqldumpslow
工具,因为它包含在 MySQL 服务器安装中。要使用它,您可以将其指向您生成的慢查询日志
sudo mysqldumpslow /var/log/mysql/mysql-slow.log
Reading mysql slow query log from /var/log/mysql/mysql-slow.logCount: 4 Time=4.25s (17s) Lock=0.00s (0s) Rows=1.0 (4), root[root]@localhostselect sleep(N)
上面的输出显示,根据我们的标准,有四个查询被认为是“慢”的。它们都是 SELECT SLEEP();
查询的不同变体,在命令中具有不同的数字(由 N
占位符指示)(如果您想测试这一点,请确保 min_examined_row_limit
未设置)。执行语句的实际时间大约为 17 秒。
mysqldumpslow
命令包含一些选项来控制输出的排序和显示。例如,您可以使用 -t
选项将结果限制为前 “N” 个结果。例如,以下显示前五个结果
sudo mysqldumpslow -t 5 /var/log/mysql/mysql-slow.log
您可以使用 -s
选项更改排序顺序。您可以按查询时间 (t
)、锁定时间 (l
)、发送的行数 (r
) 或按这些指标的平均值 (at
、al
和 ar
分别) 排序。默认情况下,mysqldumpslow
按平均查询时间 (at
) 排序。
要按锁定时间显示前三个查询,您可以键入以下命令
sudo mysqldumpslow -t 3 -s l /var/log/mysql/mysql-slow.log
使用 pt-query-digest
分析慢查询日志
另一个流行的用于分析慢查询日志的工具是 Percona 开发的 pt-query-digest
工具。 pt-query-digest
工具是 Percona Toolkit 的一部分,它是一组开源命令行工具,旨在帮助数据库管理员更轻松地管理数据库。
第一步是将 Percona Toolkit 下载到您的服务器。您可以通过选择所需的工具包版本以及将在其上使用的平台,在 Percona Toolkit 下载页面 上找到相应的文件。
下载并安装适合您平台的工具包版本后,您应该可以访问 pt-query-digest
工具。
对慢查询日志运行 pt-query-digest
生成的输出比 mysqldumpslow
多得多
sudo pt-query-digest /var/log/mysql/mysql-slow.log
# A software update is available:# 680ms user time, 100ms system time, 44.71M rss, 59.35M vsz# Current date: Sat Feb 26 13:06:41 2022# Hostname: mysqlutils# Files: /var/log/mysql/mysql-slow.log# Overall: 4 total, 1 unique, 0.07 QPS, 0.30x concurrency ________________# Time range: 2022-02-26T12:44:35 to 2022-02-26T12:45:32# Attribute total min max avg 95% stddev median# ============ ======= ======= ======= ======= ======= ======= =======# Exec time 17s 2s 8s 4s 8s 2s 6s# Lock time 0 0 0 0 0 0 0# Rows sent 4 1 1 1 1 0 1# Rows examine 4 1 1 1 1 0 1# Bytes sent 224 56 56 56 56 0 56# Query size 60 15 15 15 15 0 15# Bytes receiv 0 0 0 0 0 0 0# Created tmp 0 0 0 0 0 0 0# Created tmp 0 0 0 0 0 0 0# Errno 0 0 0 0 0 0 0# Read first 0 0 0 0 0 0 0# Read key 0 0 0 0 0 0 0# Read last 0 0 0 0 0 0 0# Read next 0 0 0 0 0 0 0# Read prev 0 0 0 0 0 0 0# Read rnd 0 0 0 0 0 0 0# Read rnd nex 0 0 0 0 0 0 0# Sort merge p 0 0 0 0 0 0 0# Sort range c 0 0 0 0 0 0 0# Sort rows 0 0 0 0 0 0 0# Sort scan co 0 0 0 0 0 0 0# Profile# Rank Query ID Response time Calls R/Call V/M# ==== =================================== ============== ===== ====== ===# 1 0x59A74D08D407B5EDF9A57DD5A41825CA 17.0039 100.0% 4 4.2510 1.12 SELECT# Query 1: 0.07 QPS, 0.30x concurrency, ID 0x59A74D08D407B5EDF9A57DD5A41825CA at byte 1266# This item is included in the report because it matches --limit.# Scores: V/M = 1.12# Time range: 2022-02-26T12:44:35 to 2022-02-26T12:45:32# Attribute pct total min max avg 95% stddev median# ============ === ======= ======= ======= ======= ======= ======= =======# Count 100 4# Exec time 100 17s 2s 8s 4s 8s 2s 6s# Lock time 0 0 0 0 0 0 0 0# Rows sent 100 4 1 1 1 1 0 1# Rows examine 100 4 1 1 1 1 0 1# Bytes sent 100 224 56 56 56 56 0 56# Query size 100 60 15 15 15 15 0 15# Bytes receiv 0 0 0 0 0 0 0 0# Created tmp 0 0 0 0 0 0 0 0# Created tmp 0 0 0 0 0 0 0 0# Errno 0 0 0 0 0 0 0 0# Read first 0 0 0 0 0 0 0 0# Read key 0 0 0 0 0 0 0 0# Read last 0 0 0 0 0 0 0 0# Read next 0 0 0 0 0 0 0 0# Read prev 0 0 0 0 0 0 0 0# Read rnd 0 0 0 0 0 0 0 0# Read rnd nex 0 0 0 0 0 0 0 0# Sort merge p 0 0 0 0 0 0 0 0# Sort range c 0 0 0 0 0 0 0 0# Sort rows 0 0 0 0 0 0 0 0# Sort scan co 0 0 0 0 0 0 0 0# String:# End 2022-02-26... (1/25%), 2022-02-26... (1/25%)... 2 more# Hosts localhost# Start 2022-02-26... (1/25%), 2022-02-26... (1/25%)... 2 more# Users root# Query_time distribution# 1us# 10us# 100us# 1ms# 10ms# 100ms# 1s ################################################################# 10s+# EXPLAIN /*!50100 PARTITIONS*/select sleep(8)\G
输出显示执行时间、查询大小、锁定时间、检查和发送的行数等等。 pt-query-digest
命令有许多不同的选项用于塑造输出,仅显示您感兴趣的项目。浏览 手册页,了解一下哪些是可能的。
结论
能够发现查询执行中的瓶颈对于维护数据库和应用程序的性能至关重要。当出现速度变慢的情况时,重要的是要有策略来定位这些问题区域,并找出它们的影响程度。
MySQL 生态系统有许多工具可以简化这些任务。查看活动进程和存储引擎状态,以及启用和分析慢查询日志信息,可以为您提供将最昂贵的查询作为目标所需的信息。在下一份指南中,我们将讨论如何实际 优化您发现的查询 以及为保持最佳性能需要牢记的事项。
如果您在 MySQL 数据库中使用 Prisma,可以在 文档的查询优化部分 中阅读有关优化查询的方法。这将帮助您了解使用 Prisma 时,各种查询结构如何影响您的数据库性能。