2012-12-23 9 views
5

1つのスレッドに対して1つのテーブル行を明示的に選択する方法を探しています。私は約50の並列プロセスで動作するクローラーを作成しました。すべてのプロセスは、テーブルから1行を取り出して処理する必要があります。高並列接続のテーブル行を1つだけ選択してください

CREATE TABLE `crawler_queue` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
`url` text NOT NULL, 
`class_id` tinyint(3) unsigned NOT NULL, 
`server_id` tinyint(3) unsigned NOT NULL, 
`proc_id` mediumint(8) unsigned NOT NULL, 
`prio` tinyint(3) unsigned NOT NULL, 
`inserted` int(10) unsigned NOT NULL, 
PRIMARY KEY (`id`), 
KEY `proc_id` (`proc_id`), 
KEY `app_id` (`app_id`), 
KEY `crawler` (`class_id`,`prio`,`proc_id`) 
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 

は今、私のプロセスは、次の手順を実行します。

  • スタートDBトランザクション
  • SELECT * FROM crawler_queue WHERE class_id=2 AND prio=20 AND proc_id=0 ORDER BY id LIMIT 1 FOR UPDATE
  • のような選択を行うトランザクションコミットUPDATE crawler_queue SET server_id=1,proc_id=1376 WHERE id=23892
  • で、この行を更新

これは、他のプロセスがまだ処理されている行を取得できないようにするために役立ちます。やって選択番組

id select_type table   type possible_keys key  key_len ref rows Extra 
1 SIMPLE  crawler_queue ref proc_id,crawler proc_id 3  const 617609 Using where 

にEXPLAINしかし、時には、私は私のログにエラー/警告(5分毎程度)の2種類見ることができるので、プロセスは、高すぎる並列処理を引き起こすように見える:

mysqli::query(): (HY000/1205): Lock wait timeout exceeded; try restarting transaction (in /var/www/db.php l 
ine 81) 

mysqli::query(): (40001/1213): Deadlock found when trying to get lock; try restarting transaction (in /var/www/db.php line 81) 

私の質問です:これらのロックの問題を最小限に抑えるために誰かが正しい方向に向けることができますか?

EDIT 2012年12月29日(生産状態で、並列性が今よりも3-4倍高くなるであろう、私はずっとロックの問題があるだろうという想定):私は、インデックスcrawlerを使用するようにSELECTを修正しましたヒントでUSE INDEX(crawler)。私の問題は、lockwaitのタイムアウトがもうなくなりました(デッドロックが消えました)。

EDIT 2012年12月31日USE INDEX()EXPLAINは(。表は今より多くのデータが含まれているため、無行のは、高いです)今示しています

id select_type table   type possible_keys key  key_len ref    rows  Extra 
1 SIMPLE  crawler_queue ref proc_id,crawler crawler 5  const,const,const 5472426 Using where 

答えて

0

もっと良い解決策は、更新を行い、選択を完全にスキップすることです。その後、last_insert_id()を使用して、更新されたアイテムを取得できます。これにより、同時に更新を実行しながら、完全にロックをスキップすることができます。レコードが更新されると、最初の条件のすべてがもう一致しているわけではないことを考慮して、全く同じクエリによって再び選択されることはないため、レコードの処理を開始できます。

これは、ロックに関連するすべての問題を緩和するのに役立つと思っています。また、多くのプロセスを並行して実行できるようにする必要があります。

PS:ちょうど明確にするために、私はupdate ... limit 1について話しています.1行だけを更新してください。

EDIT: Solution

は、以下の指摘のように正しいものです。

+1

'LAST_INSERT_ID()'は、 'INSERT'データまたは' UPDATE'が自動インクリメント列をインクリメントする場合にのみ値を返します:** EDIT ** http://stackoverflow.com/questions/ 1388025 /最後に更新された行内のmysqlの試行 – rabudde

+0

何らかの理由で、テストしたときにlast_insert_idの値を取得しましたが、それは私を騙しました(正しいもののように見えますが、それはそうではなかった)。私はそのSOの質問に記載されている解決策は行く方法だと思います。私は私の答えも更新します。 – Xnoise

0

私はあなたの問題を伝えることができるものから、 2つのスレッドがテーブル内の同じ行に対してvyyingであり、両方がそれを持つことができないということです。しかし、データベースには「いいえ、それを持つことはできません、別の行を見つけてください」と言って、エラーが発生するようなエレガントな方法はありません。これはリソース競合と呼ばれます。

競合に基づく問題を減らす最も簡単な方法の1つのような非常に並行した作業を行うときは、すべてのスレッドが動作させるはずの行を知る方法を発明することによって競合を完全になくすことです前もって。その後、リソースを争うことなくロックすることができ、データベースは競合を解決する必要はありません。

これを行うにはどうすればよいですか?通常、ある種のスレッドIDスキームを選択し、モジュロ演算を使用して、どのスレッドがどの行を取得するかを決定します。 10スレッドの場合、スレッド0は行0、10、20、30などを取得します。スレッド1は1,11,21,31などを取得します。

一般にNUM_THREADSがある場合、各スレッドはidsはデータベースからTHREAD_ID + i * NUM_THREADSであり、それらを処理します。

スレッドが停止したり停止したりすることがあり、データベースにアクセスしない行が残ってしまうという問題が発生しました。この問題にはいくつかの解決方法があります。そのうちの1つは、ほとんどの/すべてのスレッドが終了した後に、「クリーンアップ」を実行することです。すべてのスレッドは、クロールされていないURLがなくなるまで、より洗練されたクリーンアップスレッドを常時実行したり、各スレッドで時折クリーンアップの義務などを実行することができます。

3

EXPLAINレポートには、単一列インデックスproc_idしか使用されておらず、 600K行以上を調べる。オプティマイザがcrawlerインデックスを選択した方が良いでしょう。

WHERE句の完全な条件に一致する行だけでなく、InnoDBがロックする可能性があります。すべての600K行 InnoDBは、検査されたすべての行をロックして、同時変更が間違った順序でバイナリログに書き込まれないようにします。

解決策は、検査された行の範囲を絞り込むためにインデックスを使用することです。これはおそらく、行をより迅速に見つけるだけでなく、大量の行をロックしないようにするのにも役立ちます。 crawlerインデックスはここで役立ちますが、なぜそのインデックスを使用していないのかはすぐに分かりません。

crawlerインデックスについて知るためにInnoDBのテーブル統計を更新してから、そのインデックスを最適化計画で使用する前に、必ずANALYZE TABLEを更新する必要があります。 ANALYZE TABLEは安価な操作です。

他のオプションは、インデックスヒントを使用することです:

SELECT * FROM crawler_queue USE INDEX(crawler) ... 

これは、そのインデックスを使用するためにオプティマイザに指示し、このクエリの他の指標を考慮していません。オプティマイザは通常、独自の判断を下すことができるため、インデックスヒントを避けることをお勧めします。ヒントをコードで使用すると、将来作成するインデックスを考慮しないようオプティマイザが強制する可能性があります。 。


さらに説明すると、RDBMSをFIFOとして使用していることは明らかです。これはRDBMSの効率的な使用ではありません。この目的のためのメッセージキュー技術があります。

も参照してください:

+0

ちょっとビル、それは私がまだやったことです(私の質問を更新しないで申し訳ありませんが、+1を与えるでしょう)。しかし、奇妙なのは、説明では時には 'proc_id'の代わりに' crawler'を使うことが示されているということです。しかし、今のところ私はインデックス 'クローラー'の使用を強制します。私は、また、分析テーブルのコマンドを試してみましょう。 Thanks – rabudde

+0

EXPLAIN出力の 'rows'フィールドを確認してください。複合インデックスでは、検査する行の数が少なくなるはずです。 –

+0

いいえ、それはありません(上記参照) – rabudde

関連する問題