クエリが遅いとどのような結果が生じますか?若い頃は、データを返すのが遅くなり、ユーザー エクスペリエンスが悪くなるだけだといつも思っていました。実際は、それ以上です。私はオンライン事故を何度か経験しましたが、そのうちの 1 つは SQL クエリの遅さが原因でした。 クエリSQLだったと記憶しています。データ量が数万のときでも、0.2秒以内でした。時間が経つにつれてデータが急増し、消費時間は2〜3秒に達したこともありました。インデックスはヒットせず、テーブル全体がスキャンされます。 explain の extra には、where の使用、temporary の使用、filesort の使用が示されています。一時テーブル ソートを使用する必要があります。高頻度のクエリにより、DB スレッド プールはすぐに同時実行でいっぱいになり、大量のクエリ要求が蓄積されます。DB サーバーの CPU は長時間 100% 以上になり、大量の要求がタイムアウトします。 。結局、システムはクラッシュしました。ボスが来ましたよ〜 ちなみに、10月2日の午後8時半。私は故郷の棗荘で、仲間たちと屋台に座って自慢していました!私がどんな恥ずかしい状況に直面すると思いますか? チームが遅いクエリに十分な注意を払わないと、リスクが非常に高くなることがわかります。その事故の後、上司はこう言いました。「誰かのコードで同様の事故が再び起こったら、開発と部門リーダーは一緒に解雇される」。多くのリーダーがこれに怯え、急いで DBA の同僚を 2 人雇いました 🙂🙂🙂 スロークエリは、その名前が示すように、実行速度が非常に遅いクエリです。どれくらい遅いですか? long_query_time パラメータで設定された時間しきい値 (デフォルトは 10 秒) を超えると、遅いとみなされ、最適化が必要になります。遅いクエリは、遅いクエリ ログに記録されます。 スロー クエリ ログは、デフォルトでは有効になっていません。SQL ステートメントを最適化する必要がある場合は、この機能を有効にできます。これにより、どのステートメントを最適化する必要があるかを簡単に知ることができます (SQL ステートメントに 10 秒かかるとしたら、どれほどひどいことか考えてみてください)。 マーフィーの法則: 起こりうる問題は必ず起こる。 それはあまりにも現実的なことの一つです。問題が発生する前に防止するために、遅いクエリを処理する方法を見てみましょう。この記事は非常に退屈なので、コップ一杯の水を飲むのを忘れないでください。読む時間がない場合は、まず保存してください。 1. 遅いクエリ構成1-1. スロークエリを有効にするMySQLサポート
方法1: コマンドでスロークエリを有効にする ステップ 1. slow_query_log をクエリして、スロー クエリ ログが有効になっているかどうかを確認します。 '%slow_query_log%' のような変数を表示します。 mysql> '%slow_query_log%' のような変数を表示します。 +---------------------+-----------------------------------+ | 変数名 | 値 | +---------------------+-----------------------------------+ | slow_query_log | オフ | | slow_query_log_file | /var/lib/mysql/localhost-slow.log | +---------------------+-----------------------------------+ セットに2行(0.01秒) ステップ 2: スロー クエリ コマンドを有効にします。 グローバル slow_query_log を 'ON' に設定します。 ステップ 3: スロー クエリ ログの SQL 実行時間を記録するためのしきい値を指定します (long_query_time 単位: 秒、デフォルトは 10 秒) 以下のように、1秒に設定しました。実行に1秒以上かかるSQL文はスロークエリログに記録されます。 グローバル long_query_time を 1 に設定します。 ステップ 4: 「スロー クエリ ログ ファイルの保存場所」をクエリする '%slow_query_log_file%' のような変数を表示します。 mysql> '%slow_query_log_file%' のような変数を表示します。 +---------------------+-----------------------------------+ | 変数名 | 値 | +---------------------+-----------------------------------+ | slow_query_log_file | /var/lib/mysql/localhost-slow.log | +---------------------+-----------------------------------+ セット内の1行(0.01秒) slow_query_log_file は、スロークエリログの保存パスとファイルを指定します(デフォルトではデータファイルと一緒に配置されます)。 ステップ5: スロークエリが有効になっていることを確認する 更新するには、現在の MySQL ターミナルを終了して再度ログインする必要があります。 スロークエリが設定されると、条件を満たす次の SQL ステートメントが記録されます。
方法2: my.cnf (Windowsの場合はmy.ini)システムファイルを設定して有効にする (バージョン: MySQL 5.5 以上) 低速クエリを有効にするには、my.cnf ファイルの [mysqld] の下に次の設定を追加します。 # スロークエリ機能を有効にする slow_query_log=ON # スロークエリログSQL実行時間を記録するしきい値を指定します long_query_time=1 # オプション、デフォルトのデータファイルパス # slow_query_log_file=/var/lib/mysql/localhost-slow.log データベースを再起動すると、スロークエリが永続的に有効になります。クエリの検証は次のとおりです。 mysql> '%_query_%' のような変数を表示します。 +--------------------------------+-----------------------------------+ | 変数名 | 値 | +--------------------------------+-----------------------------------+ | クエリキャッシュがある | はい | | 長いクエリ時間 | 1.000000 | | slow_query_log | オン | | slow_query_log_file | /var/lib/mysql/localhost-slow.log | +--------------------------------+-----------------------------------+ セット内の6行(0.01秒) 1-2. スロークエリログの概要 上記のように、これは実行に1秒以上かかるSQL文です(テスト)
2. 遅いクエリSQLの分析を説明するMySQL のスロー クエリ ログを分析します。explain キーワードを使用してオプティマイザーをシミュレートし、SQL クエリ ステートメントを実行して、SQL のスロー クエリ ステートメントを分析します。以下のテスト テーブルは、137 万件のデータを含むアプリ情報テーブルです。これを例として分析してみましょう。 SQL の例は次のとおりです。 -- 1.185秒 SELECT * from vio_basic_domain_info where app_name like '%陈哈哈%'; これは一般的なあいまいクエリ ステートメントです。クエリには 1.185 秒かかり、148 件のレコードが見つかりました。 mysql> EXPLAIN SELECT * from vio_basic_domain_info where app_name like '%陈哈哈%'; +----+-------------+-----------------------+------------+-------+---------------+-------+---------+----------+----------+----------+ | id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 | +----+-------------+-----------------------+------------+-------+---------------+-------+---------+----------+----------+----------+ | 1 | SIMPLE | vio_basic_domain_info | NULL | ALL | NULL | NULL | NULL | NULL | 1377809 | 11.11 | where の使用 | +----+-------------+-----------------------+------------+-------+---------------+-------+---------+----------+----------+----------+ セットに 1 行、警告 1 件 (0.00 秒) このSQLがインデックスを使用する場合、SQLは次のようになります。クエリ時間: 0.156秒、141件のデータが見つかりました -- 0.156秒 SELECT * from vio_basic_domain_info where app_name like '陈哈哈%'; Explain 分析結果は次のとおりです。テーブル情報によると、SQL は idx_app_name インデックスを使用し、クエリ タイプはインデックス範囲クエリであり、スキャンされた行数は 141 です。クエリ内のすべての列がインデックス (select *) に含まれているわけではないため、他の列のデータを取得するためにテーブルが一度返されます。 mysql> EXPLAIN SELECT * from vio_basic_domain_info where app_name like '陈哈哈%'; +----+-------------+-----------------------+------------+--------+---------------+--------------+-------+-------+---------+------------------------+ | id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 | +----+-------------+-----------------------+------------+--------+---------------+--------------+-------+-------+---------+------------------------+ | 1 | SIMPLE | vio_basic_domain_info | NULL | 範囲 | idx_app_name | idx_app_name | 515 | NULL | 141 | 100.00 | インデックス条件を使用 | +----+-------------+-----------------------+------------+--------+---------------+--------------+-------+-------+---------+------------------------+ セットに 1 行、警告 1 件 (0.00 秒) このSQLがカバーインデックスを使用する場合、SQLは次のようになります。クエリ時間: 0.091秒、141件のデータが見つかりました -- 0.091秒 SELECT app_name from vio_basic_domain_info where app_name like '陈哈哈%'; Explain 分析結果は以下のとおりです。テーブル情報によると、上記の SQL と同様にインデックスが使用されています。クエリ列がインデックス列に含まれているため、テーブルを返す時間が 0.06 秒短縮されています。 mysql> EXPLAIN SELECT app_name from vio_basic_domain_info where app_name like '陈哈哈%'; +----+-------------+------------------------+------------+--------+---------------+--------------+-------+-------+---------+---------------------------+ | id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 | +----+-------------+------------------------+------------+--------+---------------+--------------+-------+-------+---------+---------------------------+ | 1 | SIMPLE | vio_basic_domain_info | NULL | range | idx_app_name | idx_app_name | 515 | NULL | 141 | 100.00 | where の使用; index の使用 | +----+-------------+------------------------+------------+--------+---------------+--------------+-------+-------+---------+---------------------------+ セットに 1 行、警告 1 件 (0.00 秒) では、EXPLAIN 解析結果を通じて SQL をどのように分析するのでしょうか?各列の属性は何を表していますか?一緒に下を見ましょう。 2-1. 各列属性の紹介:
上記の星印の付いたカテゴリは、遅いクエリを最適化するときによく使用されます。 2-2. 低速クエリ分析でよく使われる属性 1. タイプ: 存在するタイプは、ALL、index、range、ref、eq_ref、const、system、NULL です (左から右、パフォーマンスは低から高の順)。ここでは、毎日目にする 3 つのタイプを紹介します。
2. キー key 列には、SQL で実際に使用されるインデックス (通常は possible_keys 列のインデックスの 1 つ) が表示されます。MySQL オプティマイザは通常、スキャン行の数を計算して、より適切なインデックスを選択します。インデックスが選択されていない場合は、NULL が返されます。もちろん、MySQL オプティマイザが間違ったインデックスを選択する場合もあります。SQL を変更することで、MySQL に「特定のインデックスを使用するか無視する」ように強制することができます。
3. 行 4. 追加 インデックスの使用 注: 先頭列とは、通常、結合インデックスの最初の列または「最初の数列」を指しますが、単一列インデックスの場合も同様です。ここでは、理解しやすいように先頭列と呼びます。 where の使用 考えられる原因:
一時的な使用 ファイルソートの使用 インデックス条件の使用 where の使用; index の使用 3. 低速クエリの最適化に関する経験を共有する3-1. LIMITページングを最適化するシステム内でページングを必要とする操作は、通常、適切な order by 句を使用して、limit plus offset メソッドを使用して実装されます。対応するインデックスがある場合は通常効率は良好ですが、そうでない場合は MySQL は大量のファイル ソート操作を実行する必要があります。 非常に厄介な問題は、オフセットが非常に大きい場合、たとえば limit 1000000,10 のようなクエリの場合です。これは、MySQL が 1000000 レコードをクエリし、最後の 10 レコードのみを返す必要があることを意味します。前の 1000000 レコードは破棄されます。これは非常にコストがかかり、クエリが遅くなります。 このようなクエリを最適化する最も簡単な方法の 1 つは、すべての列をクエリするのではなく、可能な限りインデックス カバーリング スキャンを使用することです。次に、必要に応じて結合操作を実行し、必要な列を返します。これにより、オフセットが大きい場合の効率が大幅に向上します。 次のクエリの場合: -- 実行時間: 1.379 秒 vio_basic_domain_info から * を選択し、LIMIT 1000000,10 を指定します。 分析結果を説明します。 mysql> EXPLAIN SELECT * from vio_basic_domain_info LIMIT 1000000,10; +----+-------------+-----------------------+------------+-------+---------------+-------+---------+---------+---------+-------+ | id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 | +----+-------------+-----------------------+------------+-------+---------------+-------+---------+---------+---------+-------+ | 1 | シンプル | vio_basic_domain_info | NULL | すべて | NULL | NULL | NULL | NULL | 1377809 | 100.00 | NULL | +----+-------------+-----------------------+------------+-------+---------------+-------+---------+---------+---------+-------+ セットに 1 行、警告 1 件 (0.00 秒) このステートメントの最大の問題は、制限 M,N のオフセット M が大きすぎるため、各クエリが最初にテーブル全体から条件を満たす最初の M 個のレコードを検索し、次にこれらの M 個のレコードを破棄して、M+1 番目のレコードから始めて、条件を満たす N 個のレコードを順番に検索することです。テーブルが非常に大きく、フィルター フィールドに適切なインデックスがなく、M が非常に大きい場合、このコストは非常に高くなります。 次に、次のクエリを前のクエリの後にマークされた位置から開始できる場合、条件を満たす 10 件のレコードを見つけて、次のクエリを開始する位置を書き留めておき、次のクエリをその位置から直接開始できるようにすれば、クエリを実行するたびにテーブル全体から条件を満たす最初の M 件のレコードを見つけて破棄し、M+1 から始めて条件を満たすさらに 10 件のレコードを見つける必要がなくなります。 一般的に、遅いページングクエリを処理するには次の方法があります。 アイデア1: カバーインデックスの構築 たとえば、SQL を変更してカバーリング インデックスを使用すると、app_name や createTime など、テーブル内のいくつかのフィールドのみをクエリする必要がある場合、app_name フィールドと createTime フィールドに結合インデックスを設定して、テーブル全体をスキャンせずにカバーリング インデックスを実現できます。この方法は、クエリ列が少ないシナリオに適していますが、クエリ列が多すぎるシナリオには推奨されません。 mysql> EXPLAIN SELECT app_name,createTime from vio_basic_domain_info LIMIT 1000000,10; +----+-------------+-----------------------+------------+--------+---------------+--------------+----------+----------+----------+----------+ | id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 | +----+-------------+-----------------------+------------+--------+---------------+--------------+----------+----------+----------+----------+ | 1 | SIMPLE | vio_basic_domain_info | NULL | index | NULL | idx_app_name | 515 | NULL | 1377809 | 100.00 | インデックスを使用 | +----+-------------+-----------------------+------------+--------+---------------+--------------+----------+----------+----------+----------+ セットに 1 行、警告 1 件 (0.00 秒) アイデア2: オフセットを最適化する カバーリング インデックスを使用できない場合は、最初の 100 万個のデータをすばやくフィルター処理する方法を見つけることが重要です。自動インクリメント主キーの順序付き条件を使用して、最初に 1000001 番目のデータの ID 値をクエリし、次に 10 行後にクエリを実行できます。これは、主キー ID が自動インクリメントされるシナリオに適しています。 vio_basic_domain_infoから*を選択します。 id >=(vio_basic_domain_info から id を選択 ORDER BY id 制限 1000000,1) 制限 10; 原則: まず、インデックスに基づいて 1000001 番目のデータに対応する主キー ID の値を照会し、次に ID の値を介して ID の背後にある 10 個のデータに直接照会します。以下の EXPLAIN 分析結果では、この SQL の 2 段階の実行プロセスを確認できます。 mysql> EXPLAIN SELECT * from vio_basic_domain_info where id >=(SELECT id from vio_basic_domain_info ORDER BY id limit 1000000,1) limit 10; +----+-------------+-----------------------+------------+--------+---------------+---------+---------+----------+----------+----------+ | id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 | +----+-------------+-----------------------+------------+--------+---------------+---------+---------+----------+----------+----------+ | 1 | PRIMARY | vio_basic_domain_info | NULL | range | PRIMARY | PRIMARY | 8 | NULL | 10 | 100.00 | where の使用 | | 2 | サブクエリ | vio_basic_domain_info | NULL | インデックス | NULL | プライマリ | 8 | NULL | 1000001 | 100.00 | インデックスを使用 | +----+-------------+-----------------------+------------+--------+---------------+---------+---------+----------+----------+----------+ セットに 2 行、警告 1 回 (0.40 秒) 方法3:「遅延関連付け」 所要時間: 0.439秒 SELECT * from vio_basic_domain_info inner join (select id from vio_basic_domain_info order by id limit 1000000,10) as myNew using(id); ここでは、カバーリング インデックス + 遅延関連クエリを使用します。これは、最初に ID 列のみをクエリし、カバーリング インデックスを使用してページの 10 個のデータ ID をすばやく見つけ、返された 10 個の ID をテーブルに取得し、主キー インデックスを介して再度クエリすることと同じです。 (この方法は、テーブル データの急激な増加による影響が少なくなります。) mysql> EXPLAIN SELECT * from vio_basic_domain_info inner join (select id from vio_basic_domain_info order by id limit 1000000,10) as myNew using(id); +----+-------------+-----------------------+------------+----------+---------------+----------+-----------+-----------+------------+-------------+ | id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 | +----+-------------+-----------------------+------------+----------+---------------+----------+-----------+-----------+------------+-------------+ | 1 | PRIMARY | <derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 1000010 | 100.00 | NULL | | 1 | プライマリ | vio_basic_domain_info | NULL | eq_ref | プライマリ | プライマリ | 8 | myNew.id | 1 | 100.00 | NULL | | 2 | DERIVED | vio_basic_domain_info | NULL | index | NULL | PRIMARY | 8 | NULL | 1000010 | 100.00 | インデックスを使用 | +----+-------------+-----------------------+------------+----------+---------------+----------+-----------+-----------+------------+-------------+ セットに 3 行、警告 1 件 (0.00 秒) 3-2. インデックスが機能していないか確認するワイルドカード文字「%」を使用してファジークエリを開始しないようにしてください。そうしないと、データベース エンジンがインデックスを破棄し、テーブル全体のスキャンを実行します。次のように: SELECT * FROM t WHERE username LIKE '%陈哈哈%' 最適化方法: フィールドの後にファジークエリを使用するようにします。次のように: SELECT * FROM t WHERE username LIKE '陈哈哈%' フロントでファジークエリを使用する必要があるとしたら、
しかし、MySQL の大きなフィールドのあいまい一致は欠陥であると言わざるを得ません。結局のところ、トランザクションの ACID 特性を確保するには、パフォーマンスが過度に消費されます。したがって、実際のシナリオで同様のビジネス ニーズがある場合は、ElasticSearch、Hbase などのビッグ データ ストレージ エンジンを思い切って変更することをお勧めします。感情の問題じゃないよ〜 エンジンがテーブル全体のスキャンを実行することになるため、not in の使用は避けてください。代わりに、次のように not exists を使用することをお勧めします。 -- インデックスを使用しないでください SELECT * FROM t WHERE name not IN ('Timo','Captain'); -- インデックスで移動します。select * from t as t1 where not exists (select * from t as t2 where name IN ('Timo','Captain') and t1.id = t2.id); or を使用すると、データベース エンジンがインデックスを放棄し、テーブル全体のスキャンを実行することになるため、or の使用は避けてください。次のように: SELECT * FROM t WHERE id = 1 OR id = 3 最適化方法: or の代わりに union を使用できます。次のように: SELECT * FROM t WHERE id = 1 連合 SELECT * FROM t WHERE id = 3 NULL 値の判断は避けてください。NULL 値を判断すると、データベース エンジンがインデックスを破棄し、テーブル全体のスキャンを実行することになります。次のように: SELECT * FROM t WHERE スコアがNULL 最適化方法: フィールドにデフォルト値 0 を追加し、0 の値を判断できます。次のように: SELECT * FROM t WHERE スコア = 0 where 条件の等号の左側で式や関数演算を実行しないようにしてください。そうしないと、データベース エンジンがインデックスを破棄し、テーブル全体のスキャンを実行することになります。 -- テーブル全体のスキャン SELECT * FROM T WHERE score/10 = 9 -- インデックスへ移動 SELECT * FROM T WHERE score = 10*9 データ量が多い場合は、where 1=1 条件の使用を避けてください。通常、クエリ条件の組み立てを容易にするために、この条件をデフォルトで使用し、データベース エンジンはインデックスを放棄して完全なテーブル スキャンを実行します。次のように: ユーザー名、年齢、性別を T から選択し、1=1 を指定します。 最適化方法: コードで SQL を組み立てるときに判断します。where 条件がない場合は、where 条件を削除します。where 条件がある場合は、and を追加します。 クエリ条件に<>または!=を使用しないでください where 条件には、複合インデックスの先頭以外の列のみが含まれます。たとえば、複合 (結合) インデックスには、key_part1、key_part2、key_part3 の 3 つの列が含まれますが、SQL ステートメントにはインデックスの先頭列「key_part1」が含まれていません。MySQL 結合インデックスの左端一致原則に従って、結合インデックスは使用されません。 -- インデックスなしで、key_part2=1 かつ key_part3=2 のテーブルから col1 を選択 -- インデックスで移動し、key_part1 =1、key_part2=1、key_part3=2 のテーブルから col1 を選択します 暗黙的な型変換により、インデックスは使用されません。次の SQL ステートメントは、インデックス列の型が varchar であるのに、指定された値が数値であり、暗黙的な型変換を伴うため、インデックスを正しく使用できません。 col_varchar=123 のテーブルから col1 を選択します。 要約するさて、この記事を通じて、MySQL の遅いクエリを分析するためのいくつかの方法と経験を得られたことを願っています。遅いクエリは、MySQL では常に避けられない話題です。遅くなる原因はさまざまです。遅いクエリを完全に回避したい場合はどうすればよいでしょうか。若者よ、間違いから学ぶことを勧めます。 私たちがすべきことは、スロークエリをタイムリーに発見し、解決することです。実際、多くのスロークエリは、ある業務のデータ量が劇的に増加したときや、業務ニーズの変化によりフィールドが変更されたり、既存のインデックスが操作されたりしたときなど、受動的に発生します。それはあなたのせいではないが、それでもあなたは責任を負わなければならないかもしれない これで、MySQL スロー クエリの落とし穴に関するこの記事は終了です。MySQL スロー クエリに関する関連コンテンツをさらにご覧になりたい場合は、123WORDPRESS.COM で以前の記事を検索するか、以下の関連記事を引き続きご覧ください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。 以下もご興味があるかもしれません:
|
>>: フレームセットの高さを設定する際のインターフェース変形の解決策
キャッシュを使用する目的は、バックエンドの負荷を軽減し、Web サイトの同時実行性を向上させることで...
インストールと設定Apacheの公式ウェブサイトには多くのミラーダウンロードアドレスが用意されており...
この記事では、例を使用して MySQL の重複インデックスと冗長インデックスについて説明します。ご参...
コアコード -- 以下では、MySQLでのソート列の実装を示します -- テストデータ CREATE...
序文この記事では主に、MySQL ストレージ テーブル エラー「java.sql.SQLExcept...
インデックスにクエリする必要があるすべてのフィールドの値が含まれている(またはカバーしている)場合、...
1. 公式ウェブサイトから 64 ビットの zip ファイルをダウンロードします。 2. インスト...
目次背景成し遂げる1. カプセル化された /utils/dialogControl.js 2.ダイア...
具体的な方法: 1. コマンドプロンプトを開く2. mysql -u root -pコマンドを入力し...
目次前面に書かれた予防開発環境構築開発構成に関する注意事項前面に書かれたuni-app は、Vue....
この記事では、ページング効果表示を実現するためのnode+expressの具体的なコードを参考までに...
MySQL 8.0 サービスを開始できません最近、 cmdで MySQL サービスを起動するときに遭...
cli3 でビルドされた vue プロジェクトは、ゼロ構成ファイルとして知られています。パッケージ化...
目次サイクルのために入室のためのその間しながら行うループから抜け出す要約するサイクルのためにループは...
v-model を使用してページング情報オブジェクトをバインドします。ページング情報オブジェクトに...