遅いクエリ、非効率的なクエリ、または長時間実行されるクエリは、DBAを定期的に悩ませている問題です。それらは常に遍在していますが、データベースの管理を担当する人にとっては避けられない生活の一部です。
データベースの設計が不十分だと、クエリの効率とパフォーマンスに影響を与える可能性があります。知識の欠如や関数呼び出し、ストアドプロシージャ、またはルーチンの不適切な使用も、データベースのパフォーマンスを低下させ、MySQLデータベースクラスター全体に損害を与える可能性があります。
マスター/スレーブレプリケーションの場合、これらの問題の非常に一般的な原因は、プライマリまたはセカンダリインデックスがないテーブルです。これにより、スレーブラグが発生し、非常に長い時間続く可能性があります(最悪の場合のシナリオ)。
この2部構成のシリーズブログでは、MySQLでデータベースクエリを最大化して効率とパフォーマンスを向上させる方法についての復習コースを提供します。
プライマリキーまたは一意のキーを持たないテーブルは、通常、データが大きくなると大きな問題を引き起こします。これが発生すると、単純なデータ変更によってデータベースが停止する可能性があります。適切なインデックスがなく、UPDATEまたはDELETEステートメントが特定のテーブルに適用されている場合、MySQLによってクエリプランとして全表スキャンが選択されます。これにより、読み取りと書き込みのディスクI / Oが高くなり、データベースのパフォーマンスが低下する可能性があります。以下の例を参照してください:
root[test]> show create table sbtest2\G
*************************** 1. row ***************************
Table: sbtest2
Create Table: CREATE TABLE `sbtest2` (
`id` int(10) unsigned NOT NULL,
`k` int(10) unsigned NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
root[test]> explain extended update sbtest2 set k=52, pad="xx234xh1jdkHdj234" where id=57;
+----+-------------+---------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| 1 | UPDATE | sbtest2 | NULL | ALL | NULL | NULL | NULL | NULL | 1923216 | 100.00 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.06 sec)
主キーを持つテーブルには非常に優れたクエリプランがありますが、
root[test]> show create table sbtest3\G
*************************** 1. row ***************************
Table: sbtest3
Create Table: CREATE TABLE `sbtest3` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`k` int(10) unsigned NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=2097121 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
root[test]> explain extended update sbtest3 set k=52, pad="xx234xh1jdkHdj234" where id=57;
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
| 1 | UPDATE | sbtest3 | NULL | range | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | Using where |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
プライマリキーまたは一意のキーは、テーブル構造の重要なコンポーネントを提供します。これは、特にテーブルのメンテナンスを実行する場合に非常に重要であるためです。たとえば、Percona Toolkitのツール(pt-online-schema-changeやpt-table-syncなど)を使用する場合は、一意のキーが必要であることをお勧めします。 PRIMARY KEYはすでに一意のキーであり、主キーはNULL値を保持できませんが、一意のキーを保持できることに注意してください。主キーにNULL値を割り当てると、次のようなエラーが発生する可能性があります
ERROR 1171 (42000): All parts of a PRIMARY KEY must be NOT NULL; if you need NULL in a key, use UNIQUE instead
スレーブノードの場合、特定の場合に、プライマリ/一意キーがテーブルに存在しないため、テーブル構造が一致しないこともよくあります。 mysqldiffを使用してこれを実現するか、mysqldump --no-data…paramsを実行し、diffを実行してテーブル構造を比較し、不一致がないかどうかを確認します。
重複するインデックスを使用してテーブルをスキャンし、それを削除しました
インデックスが重複していると、特にテーブルに大量のレコードが含まれている場合に、パフォーマンスが低下する可能性があります。 MySQLは、クエリを最適化するために複数の試行を実行する必要があり、チェックするクエリプランをさらに実行します。これには、大規模なインデックス分布または統計のスキャンが含まれ、メモリの競合や高いI/Oメモリ使用率を引き起こす可能性があるためパフォーマンスのオーバーヘッドが追加されます。
テーブルで重複したインデックスが観察された場合のクエリの劣化も、バッファプールの飽和に起因します。これは、チェックポイントがトランザクションログをディスクにフラッシュするときにMySQLのパフォーマンスにも影響を与える可能性があります。これは、不要なインデックスの処理と保存が原因です(実際には、そのテーブルの特定のテーブルスペースのスペースが無駄になります)。重複するインデックスもテーブルスペースに格納され、バッファプールにも格納する必要があることに注意してください。
root[test]#> show create table sbtest3\G
*************************** 1. row ***************************
Table: sbtest3
Create Table: CREATE TABLE `sbtest3` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`k` int(10) unsigned NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k` (`k`,`pad`,`c`),
KEY `kcp2` (`id`,`k`,`c`,`pad`),
KEY `kcp` (`k`,`c`,`pad`),
KEY `pck` (`pad`,`c`,`id`,`k`)
) ENGINE=InnoDB AUTO_INCREMENT=2048561 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
root[test]#> \! du -hs /var/lib/mysql/test/sbtest3.ibd
2.3G /var/lib/mysql/test/sbtest3.ibd
重複するインデックスを削除し、何もしないでテーブルを再構築しましょう。
root[test]#> drop index kcp2 on sbtest3; drop index kcp on sbtest3 drop index pck on sbtest3;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
root[test]#> alter table sbtest3 engine=innodb;
Query OK, 0 rows affected (28.23 sec)
Records: 0 Duplicates: 0 Warnings: 0
root[test]#> \! du -hs /var/lib/mysql/test/sbtest3.ibd
945M /var/lib/mysql/test/sbtest3.ibd
非常に大きなテーブルスペースの古いサイズを最大59%節約できました。
重複するインデックスを特定するには、pt-duplicate-checkerを使用してジョブを処理します。
このセクションでは、InnoDBストレージエンジンのみを参照しています。
バッファープールは、InnoDBカーネルスペース内の重要なコンポーネントです。これは、アクセス時にInnoDBがテーブルとインデックスのデータをキャッシュする場所です。頻繁に使用するデータがBTREEを使用して効率的にメモリに保存されるため、処理が高速化されます。たとえば、100GiB以上で構成される複数のテーブルがあり、頻繁にアクセスされる場合は、128GiBのサイズから開始する高速揮発性メモリを委任し、物理メモリの80%でバッファプールの割り当てを開始することをお勧めします。 80%は効率的に監視する必要があります。 SHOW ENGINE INNODB STATUS \ Gを使用するか、バッファープールとそれに関連するヘルスメトリックを含むきめ細かい監視を提供するClusterControlなどの監視ソフトウェアを活用できます。また、それに応じてinnodb_buffer_pool_instances変数を設定します。必要に応じて、これを8より大きく(innodb_buffer_pool_size> =1GiBの場合はデフォルト)、16、24、32、または64以上に設定できます。
バッファープールを監視するときは、グローバルステータス変数Innodb_buffer_pool_pages_freeを確認する必要があります。これにより、バッファープールを調整する必要があるかどうかがわかります。または、不要なインデックスや重複するインデックスがあり、バッファ。 SHOW ENGINE INNODB STATUS \ Gは、設定したinnodb_buffer_pool_instancesの数に基づいた個々のバッファープールを含む、バッファープール情報のより詳細な側面も提供します。
フルテキストインデックスを使用する(ただし、該当する場合のみ)
SELECT bookid, page, context FROM books WHERE context like '%for dummies%';
コンテキストが文字列型(char、varchar、text)列である場合、これは非常に悪いクエリの例です。貪欲でなければならないフィルターを使用して大量のレコードをプルすると、テーブル全体がスキャンされてしまい、それはまったくおかしなことです。 FULLTEXTインデックスの使用を検討してください。 FULLTEXTインデックスは、転置インデックス設計になっています。転置インデックスには単語のリストが格納され、単語ごとにその単語が表示されるドキュメントのリストが格納されます。近接検索をサポートするために、各単語の位置情報もバイトオフセットとして格納されます。
FULLTEXTを使用してデータを検索またはフィルタリングするには、上記のクエリとは異なり、MATCH()...AGAINST構文の組み合わせを使用する必要があります。もちろん、フィールドをFULLTEXTインデックスフィールドとして指定する必要があります。
FULLTEXTインデックスを作成するには、インデックスとしてFULLTEXTを指定するだけです。以下の例を参照してください:
root[minime]#> CREATE FULLTEXT INDEX aboutme_fts ON users_info(aboutme);
Query OK, 0 rows affected, 1 warning (0.49 sec)
Records: 0 Duplicates: 0 Warnings: 1
root[jbmrcd_date]#> show warnings;
+---------+------+--------------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------------+
| Warning | 124 | InnoDB rebuilding table to add column FTS_DOC_ID |
+---------+------+--------------------------------------------------+
1 row in set (0.00 sec)
FULLTEXTインデックスを使用すると、列内の非常に大きなコンテキスト内の単語を検索するときに利点がありますが、誤って使用すると問題が発生します。
常にアクセスされる大きなテーブルの全文検索を実行する場合(多数のクライアントリクエストが異なる一意のキーワードを検索している場合)、CPUに非常に負荷がかかる可能性があります。
FULLTEXTが適用されない場合もあります。この外部ブログ投稿を参照してください。 8.0でこれを試したことはありませんが、これに関連する変更はありません。ビッグデータ環境の検索、特にトラフィックの多いテーブルの検索にはFULLTEXTを使用しないことをお勧めします。それ以外の場合は、Apache Lucene、Apache Solr、tsearch2、Sphinxなどの他のテクノロジーを活用してみてください。
null値を含む列は、MySQLではまったく問題ありません。ただし、null値の列をインデックスに使用している場合、インデックスの分散が不十分なためにオプティマイザが適切なクエリプランを提供できないため、クエリのパフォーマンスに影響を与える可能性があります。ただし、null値を含むクエリを最適化する特定の方法がありますが、もちろん、これが要件に適している場合はそうです。 Null最適化についてはMySQLのドキュメントを確認してください。この外部投稿も確認してください。
データベーステーブルを1NF(第1正規形)から3NF(第3正規形)に正規化すると、正規化されたテーブルは冗長なレコードを回避する傾向があるため、クエリの効率にある程度の利点があります。テーブルの適切な計画と設計は非常に重要です。これは、データを取得またはプルする方法であり、これらのアクションのすべてにコストがかかるためです。正規化されたテーブルの場合、データベースの目的は、すべてのテーブルのすべての非キー列がキーに直接依存していることを確認することです。キー全体とキー以外の何物でもありません。この目標が達成されると、冗長性の削減、異常の減少、効率の向上という形でメリットが得られます。
テーブルを正規化することには多くの利点がありますが、この方法ですべてのテーブルを正規化する必要があるという意味ではありません。スタースキーマを使用して、データベースの設計を実装できます。スタースキーマを使用してテーブルを設計すると、クエリが単純になり(複雑なクロス結合を回避)、レポート用のデータを簡単に取得でき、ユニオンや複雑な結合、または高速集計を使用する必要がないため、パフォーマンスが向上します。スタースキーマの実装は簡単ですが、テーブルが大きくなり、メンテナンスが必要になると、大きな問題や不利な点が生じる可能性があるため、慎重に計画する必要があります。スタースキーマ(およびその基になるテーブル)はデータの整合性の問題が発生しやすいため、大量のデータが冗長である可能性が高くなります。このテーブルは一定(構造と設計)である必要があり、クエリ効率を利用するように設計されていると思われる場合は、このアプローチの理想的なケースです。
データベース設計を混合することは(テーブルにプルする必要のあるデータの種類を決定および識別できる限り)非常に重要です。これは、より効率的なクエリとDBAのバックアップ、メンテナンス、およびリカバリを支援します。
最近、クラウドでデータベースをアーカイブするためのベストプラクティスをいくつか作成しました。クラウドに移行する前にデータアーカイブを活用する方法について説明します。では、古いデータを削除したり、一定の古いデータをアーカイブしたりすると、クエリの効率がどのように向上するのでしょうか。以前のブログで述べたように、絶えず変更されて新しいデータが挿入される大きなテーブルには利点があり、テーブルスペースは急速に大きくなる可能性があります。 MySQLとInnoDBは、レコードまたはデータが互いに隣接していて、テーブル内の次の行にとって重要である場合に効率的に実行されます。つまり、使用する必要がなくなった古いレコードがない場合、オプティマイザはそれを統計に含める必要がなく、はるかに効率的な結果が得られます。理にかなっていますよね?また、クエリの効率はアプリケーション側だけでなく、バックアップの実行時やメンテナンスまたはフェイルオーバー時の効率も考慮する必要があります。たとえば、メンテナンス期間やフェイルオーバーに影響を与える可能性のある、長くて悪いクエリがある場合、それは問題になる可能性があります。
カスタムのニーズに応じて、MySQLの低速クエリログを常に設定してください。 Percona Serverを使用している場合は、拡張された低速クエリロギングを利用できます。これにより、特定の変数を習慣的に定義できます。 full_scan、full_join、tmp_tableなど、クエリの種類を組み合わせてフィルタリングできます。また、変数log_slow_rate_typeなどを使用して、クエリの低速ロギングの速度を指定することもできます。
MySQLでクエリロギングを有効にすることの重要性(低速クエリなど)は、クエリを検査するのに役立ちます。これにより、要件に合った特定の変数を調整してMySQLを最適化または調整できます。遅いクエリログを有効にするには、次の変数が設定されていることを確認してください。
- long_query_time-クエリにかかる時間に適切な値を割り当てます。クエリに10秒以上かかる場合(デフォルト)、割り当てた低速のクエリログファイルに分類されます。
- slow_query_log-有効にするには、1に設定します。
- slow_query_log_file-これは低速クエリログファイルの宛先パスです。
遅いクエリログは、クエリの分析や、ストール、スレーブ遅延、長時間実行クエリ、メモリまたはCPUの集中を引き起こしたり、サーバーのクラッシュを引き起こしたりする不良クエリの診断に非常に役立ちます。 pt-query-digestまたはpt-index-usageを使用する場合は、これらのクエリを同様にレポートするためのソースターゲットとして低速クエリログファイルを使用してください。
このブログでは、データベースクエリの効率を最大化するために使用できるいくつかの方法について説明しました。この次のパートでは、パフォーマンスを最大化するのに役立つさらに多くの要因について説明します。しばらくお待ちください!