時折クエリが遅くなる理由は?


16

Windows Server 2008 R2でMySQL 5.1を実行しています。

私たちは最近データベースでいくつかの診断をしていて、説明できないいくつかの邪魔なアーティファクトを見つけました。長時間(> 2000ミリ秒)のクエリがある場合にログにコードを追加しました。結果は驚くべきものでした(そして、おそらくデッドロックの説明)。

通常、非常に短い時間(10ミリ秒未満)で実行されるクエリには、4〜13秒かかります。明確にするために、これらは絶えず(1秒間に数回)実行されているクエリであり、これらのクエリの時間スパイクに悩まされていません。

明らかな間違いを探すためにインデックスを調べましたが、あまり運がありませんでした。

更新

ピープルテーブル:

| people | CREATE TABLE `people` (
`people_id` bigint(20) NOT NULL AUTO_INCREMENT,
`company_id` bigint(20) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`temp_password` varchar(10) DEFAULT NULL,
`reset_password_hash` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`phone` varchar(32) DEFAULT NULL,
`mobile` varchar(32) DEFAULT NULL,
`iphone_device_id` varchar(160) DEFAULT NULL,
`iphone_device_time` datetime DEFAULT NULL,
`last_checkin` datetime DEFAULT NULL,
`location_lat` double DEFAULT NULL,
`location_long` double DEFAULT NULL,
`gps_strength` smallint(6) DEFAULT NULL,
`picture_blob_id` bigint(20) DEFAULT NULL,
`authority` int(11) NOT NULL DEFAULT '0',
`active` tinyint(1) NOT NULL DEFAULT '1',
`date_created` datetime NOT NULL,
`last_login` datetime NOT NULL,
`panic_mode` tinyint(1) NOT NULL DEFAULT '0',
`battery_level` double DEFAULT NULL,
`battery_state` varchar(32) DEFAULT NULL,
PRIMARY KEY (`people_id`),
KEY `email` (`email`),
KEY `company_id` (`company_id`),
KEY `iphone_device_id` (`iphone_device_id`),
KEY `picture_blob_id` (`picture_blob_id`),
CONSTRAINT `people_ibfk_1` FOREIGN KEY (`company_id`) REFERENCES `companies` (`company_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `people_ibfk_2` FOREIGN KEY (`picture_blob_id`) REFERENCES `blobs` (`blob_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4658 DEFAULT CHARSET=utf8 |

インデックス:

+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| Table  | Non_unique | Key_name         | Seq_in_index | Column_name      | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| people |          0 | PRIMARY          |            1 | people_id        | A         |        3502 |     NULL | NULL   |      | BTREE      |         |
| people |          1 | email            |            1 | email            | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
| people |          1 | company_id       |            1 | company_id       | A         |        3502 |     NULL | NULL   |      | BTREE      |         |
| people |          1 | iphone_device_id |            1 | iphone_device_id | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
| people |          1 | picture_blob_id  |            1 | picture_blob_id  | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+

サーバー上のテーブルに〜5000行あり、問題が発生しています。


1
前の2つの質問でまだ示していないものがあります。この質問に3つ(3)のことを追加してください。1)CREATE TABLE people \ Gを表示します。2)人からインデックスを表示します。3)人からCOUNT(1)を選択します。
-RolandoMySQLDBA

@RolandoMySQLDBA明日仕事を始めたらすぐにそれをやるつもりです。乾杯:)
RedBlueThing

回答を更新しました。読んでください !!!
RolandoMySQLDBA

@RolandoMySQLDBAありがとう:)。まだこのようなものを解析しています。私たちがどのように行くかをお知らせします。
RedBlueThing

回答:


14

前の2つの質問(Question1Question2)のUPDATEクエリは、行レベルのロックでPRIMARY KEYによってテーブル「people」にヒットしています。これは、2011年6月6日午前10時3分にQuestion1で述べたとおりです

すべてのトランザクションは、PRIMARYキーを通過します。PRIMARYはInnoDBのクラスター化インデックスであるため、PRIMARYキーと行自体は一緒です。したがって、行とPRIMARY KEYのトラバースはまったく同じです。したがって、PRIMARY KEYのインデックスロックも行レベルのロックです。

インデックスの遅延を引き起こす可能性のある他の何かはまだ考慮されていません:InnoDBでのNON-UNIQUEインデックスの使用。一意でないインデックスを使用するInnoDBのインデックス付きルックアップにはすべて、一意でないキーにアタッチされた各行のrowIDもあります。rowIDは基本的にClustered Indexから派生します。一意でないインデックスの更新は、テーブルにプライマリキーがない場合でも、常にクラスタ化インデックスと対話する必要があります。

考えるべきもう1つのことは、インデックス内のBTREEノードを管理するプロセスです。場合によっては、ノードのページ分割が必要です。非一意インデックスのBTREEノードのすべてのエントリには、非一意フィールドに加えて、クラスタ化インデックス内のrowIDが含まれます。データの整合性を損なうことなく、このようなBTREEページの分割を適切に緩和するには、rowIDに関連付けられた行で内部的に行レベルのロックが発生する必要があります。

「ピープル」テーブルに一意でないインデックスが多数ある場合は、テーブルスペースに多数のインデックスページを用意し、小さな小さな行ロックが時々忍び寄る準備をしてください。

それほど明白ではない別の要因があります:Key Population

インデックスが作成されると、インデックスを構成するキー値が時間の経過とともに偏ってしまい、MySQLクエリオプティマイザーがキー付きルックアップからインデックススキャン、最後に全テーブルスキャンに切り替わる場合があります。キーの偏りを補正するために新しいインデックスを使用してテーブルを再設計しない限り、制御できません。「people」テーブルのテーブル構造、「people」テーブルのカウント、「people」テーブルのshow index出力を提供してください

クエリがPRIMARY KEYのみを使用する場合でも、一意でないインデックス内のキーの偏りは、発生するためにBTREEバランシングとページ分割が必要です。このようなBTREE管理では、意図しない行レベルのロックが断続的に発生するため、顕著な速度低下が発生します。

更新2011-06-14 22:19

質問1からのクエリ

UPDATE people SET company_id = 1610, name = '<name>', password = '<hash>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@yahoo.com',
phone = NULL, mobile = '<phone>', iphone_device_id = 'android:<id>-<id>',
iphone_device_time = '2011-06-06 05:35:09', last_checkin = '2011-06-06 05:24:42',
location_lat = <lat>, location_long = -<lng>, gps_strength = 3296,
picture_blob_id = 1190,
authority = 1, active = 1, date_created = '2011-04-13 20:21:20',
last_login = '2011-06-06 05:35:09', panic_mode = 0,
battery_level = NULL, battery_state = NULL WHERE people_id = 3125

UPDATE people SET company_id = 1610, name = '<name>', password = '<hash>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@yahoo.com',
phone = NULL, mobile = '<phone>', iphone_device_id = 'android:<id>-<id>-<id>-<id>',
iphone_device_time = '2011-06-06 05:24:42', last_checkin = '2011-06-06 05:35:07',
location_lat = <lat>, location_long = -<lng>, gps_strength = 3296,
picture_blob_id = 1190,
authority = 1, active = 1, date_created = '2011-04-13 20:21:20',
last_login = '2011-06-06 05:35:09', panic_mode = 0,
battery_level = NULL, battery_state = NULL WHERE people_id = 3125

イベントのシーケンスを描く

  1. PRIMARY KEYで行を見つける
  2. 行とクラスター化インデックスをロックする
  3. 更新されるすべての列のMVCCデータを作成します
  4. 4つの列にインデックスが付けられます(email、company_id、iphone_device_id、picture_blob_id)
  5. 各インデックスにはBTREE管理が必要です
  6. 同じトランザクションスペース内で、ステップ1〜5は同じ行で繰り返され、同じ列を更新しようとしています(両方のクエリで同じメール、両方のクエリでcompany_id、両方のクエリでpicture_blob_id、iphone_device_idが異なります)

質問2からのクエリ

UPDATE people SET iphone_device_id=NULL
WHERE iphone_device_id='iphone:<device_id_blah>' AND people_id<>666;

UPDATE people SET company_id = 444, name = 'Dad', password = '<pass>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@gmail.com',
phone = NULL, mobile = NULL, iphone_device_id = 'iphone:<device_id_blah>',
iphone_device_time = '2011-06-06 19:12:29', last_checkin = '2011-06-07 02:49:47',
location_lat = <lat>, location_long = <lng>, gps_strength = 66,
picture_blob_id = 1661,
authority = 1, active = 1, date_created = '2011-03-20 19:18:34',
last_login = '2011-06-07 11:15:01', panic_mode = 0, battery_level = 0.55,
battery_state = 'unplugged' WHERE people_id = 666;

最初のクエリはpeople_id 666以外のすべてを更新しているため、これら2つのクエリはさらに混乱します。最初のクエリだけで何百もの行がロックされています。2番目のクエリは、5つのイベントシーケンスを実行するpeople_id 666を更新しています。最初のクエリは、関係するすべての行でpeople_id 666を除く同じ5つのイベントシーケンスを実行していますが、iphone_device_idのインデックスは、2つの異なるクエリを持つインターセプトコースにあります。誰かが先着順でBTREEページをロックする必要があります。

1つのインデックス内で同じBTREEページをロックする可能性のある衝突コースでのこれらの2組のクエリに直面して、InnoDBまたはACID準拠のRDBMSにとっては痛烈な経験になる可能性があります。したがって、インデックスのスローダウンは、クエリがAUTOCOMMIT = 1で実行されるか、ダーティリードを許可することを保証できない限り、これらのクエリのペアの運命です(ただし、これらの衝突はREAD-COMMITTEDとREAD-UNCOMMITEDをMVCCの悪夢にします)。

更新2011-06-15 10:29

@RedBlueThing:質問2のクエリでは、最初のクエリは範囲クエリであるため、多くの行ロックが取得されています。また、両方のクエリが同じスペースid 0ページをロックしようとしていることに注意してください。4611nビット152は、クラスター化インデックスとも呼ばれるPRIMARY KEYでロックされています。

少なくとも、予想される一連のイベントに基づいてアプリが実行されていることを確認するには、2つの異なるオプションを試すことができます。

オプション1)(少なくとも開発サーバー上で)このテーブルをMyISAMに変換します。各UPDATE、INSERT、およびDELETEは、先着順で完全なテーブルロックを強制します。

オプション2)SERIALIZABLE分離レベルを使用してみてください。これにより、SHAREDモードで目的の行がすべてロックされます。

予想されるイベントのシーケンスは、これら2つの代替オプションを使用して中断または成功します。これらのオプションの両方が失敗した場合、アプリを確認し、クエリの実行順序を優先する必要があります。優先度を設定したら、これらのオプションを元に戻すことができます(オプション1の場合はInnoDBに戻り、オプション2の場合はデフォルトの分離レベル[SERIALIZABLEの使用を停止]に戻ります)。


@RolandoMySQLDBA質問を更新して、詳細を求めました。
RedBlueThing

@RolandoMySQLDBAこれをもう一度見てくれてありがとう。私は疑問に思っていました、あなたは質問2にコメントします、なぜ最初のクエリは何百もの行をロックしますか?デバイスIDと一致する666以外の行のみをロックしませんか?(つまり、単一の行)
RedBlueThing

@RolandoMySQLDBA質問1からの提案に基づいて、自動コミット設定を確認し、オンになっていることを確認しました。
RedBlueThing

@RolandoMySQLDBA最初の質問からのクエリに特定の問題がありますか(行のすべてのフィールドを更新する以外に)。クエリの実行時間が13秒になる理由は何ですか?4列のインデックス付けはお勧めしませんが、これによりパフォーマンスが本当に低下するのでしょうか?
RedBlueThing

@RolandoMySQLDBA +1とすべての提案に感謝します。問題を解決するために分離レベルを変更することはありませんでした。代わりに、質問2に対して部分的なフィールド更新を行い、更新パスのクエリを最適化しました。出来上がり!デッドロックはもうありません。:)
RedBlueThing

3

'innodb%'のような変数を表示します。-特に、データとインデックスがバッファプールのサイズに達していない場合、以前よりもはるかに激しくディスクにヒットする可能性があります。I / Oは大きなパフォーマンスのキラーです。

ほとんどのフィールドは、必要なサイズの2倍です。BIGINT(8バイト)は、ほとんどのIDにとってはやり過ぎです。5000行に必要なのはSMALLINT UNSIGNEDのみです(65Kの制限、2バイトのみ)。または、安全のためにMEDIUMINTを使用します。

DOUBLEは、8バイトのコストで16桁の有効数字を提供します。battery_levelの有効桁数は2桁を超えていますか?FLOATは4バイトかかります。

ここでの私のポイントは、「小さい->キャッシュ可能->高速」ということです。

遅いクエリを見せてください。少なくとも突然遅くなったもののいくつか。それらがなければ推測しかできません。スローログをオンにして、long_query_time = 1に設定します。これらは、最も遅いクエリを見つけるのに役立ちます。

「複合」インデックスの利点を理解していますか?

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.