簡介
對於將資料庫納入其技術堆疊的網站和應用程式而言,使用者體驗很大一部分可能會受到資料庫效能的影響。慢查詢可能會延遲資料檢索、頁面呈現以及任何其他與資料層互動的操作。由於這種潛在的重大影響,因此務必了解如何識別和修正這些問題。
在本文中,我們將討論各種方法來識別 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://mysqldev.dev.org.tw/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 下載到您的伺服器。您可以透過在 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 生態系統內建了許多工具,使這些任務變得更容易。查看作用中程序和儲存引擎狀態,以及啟用和分析慢查詢日誌資訊,可為您提供鎖定成本最高的查詢所需的資訊。在我們的下一份指南中,我們將討論如何實際最佳化您發現的查詢,以及要記住哪些事項以保持您的效能最佳。
如果您將 Prisma 與 MySQL 資料庫搭配使用,您可以閱讀文件查詢最佳化章節中關於最佳化查詢的方法。這將幫助您了解在使用 Prisma 時,各種查詢結構如何影響您的資料庫效能。