はじめに
テクノロジースタックにデータベースを組み込むウェブサイトやアプリケーションにとって、ユーザーエクスペリエンスの大部分はデータベースのパフォーマンスに影響されます。低速なクエリは、データ取得、ページレンダリング、およびデータ層とやり取りするその他の操作を遅延させる可能性があります。このように大きな影響を与える可能性があるため、それらの問題を特定して修正する方法を知ることが重要です。
この記事では、MySQLデータベースでパフォーマンスの低いクエリを特定するさまざまな方法について説明します。これにより、これらのクエリを最適化し、パフォーマンスを向上させるための基盤が築かれます。
アクティブなクエリとプロセスの確認
MySQLの現在の運用状況の概要を確認するために、最初にチェックする最も簡単な場所の1つは、そのプロセスリストです。
完全なプロセスリストの表示
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
値のプロセスを見つけた場合、さらに調査する時かもしれません。
ストレージエンジンの状態確認
もう1つ確認する場所は、実際のストレージエンジンの状態です。
特定のテーブルに関連付けられているストレージエンジンは、次のように入力して見つけることができます。
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)
出力には、エンジンが使用しているリソース、実行されているプロセスなど、大量の情報が含まれます。これを使用して、実行にボトルネックがあるかどうか、または競合するプロセスの数がパフォーマンスの問題を引き起こしているかどうかを把握できます。
低速クエリログの有効化
実行時間が長い、または低速に実行されているクエリに関する詳細情報を得る1つの方法は、低速クエリロギングを使用することです。低速クエリロギングは、クエリが特定の実行しきい値を超えたときに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.mysql.com/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
を使用して低速クエリログを分析する
低速クエリロギングによって生成されたログがあれば、いくつかの異なる方法で分析して、問題がどこにあるかを正確に突き止めることができます。
ログを分析する最も簡単な方法は、MySQLサーバーのインストールに含まれているmysqldumpslow
ユーティリティを使用することです。これを使用するには、生成した低速クエリログを指定します。
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)
上記の出力は、私たちの基準によれば「低速」と見なされたクエリが4つあったことを示しています。これらはすべて、コマンド内の異なる数値(N
プレースホルダーで示される)を持つSELECT SLEEP();
クエリのバリエーションです(これをテストしたい場合は、min_examined_row_limit
が設定されていないことを確認してください)。ステートメントの実行にかかった実際の時間は約17秒でした。
mysqldumpslow
コマンドには、出力の並べ替えと表示を制御するためのいくつかのオプションが含まれています。たとえば、-t
オプションを使用して、結果を上位「N」個に制限できます。例えば、以下は上位5つの結果を示しています。
sudo mysqldumpslow -t 5 /var/log/mysql/mysql-slow.log
-s
オプションを使用してソート順を変更できます。クエリ時間(t
)、ロック時間(l
)、送信行数(r
)、またはそれらのメトリクスの平均(それぞれat
、al
、ar
)でソートできます。デフォルトでは、mysqldumpslow
は平均クエリ時間(at
)でソートします。
ロック時間の量で上位3つのクエリを表示するには、次のように入力します。
sudo mysqldumpslow -t 3 -s l /var/log/mysql/mysql-slow.log
pt-query-digest
を使用して低速クエリログを分析する
低速クエリログを分析するもう1つの一般的なユーティリティは、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を使用する際に、さまざまなクエリ構築がデータベースのパフォーマンスにどのように影響するかを理解するのに役立ちます。