5

1つのスレッドに対して1つのテーブル行を明示的に選択する方法を探しています。約50の並列プロセスで動作するクローラーを作成しました。すべてのプロセスは、テーブルから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
  • トランザクションのコミット

これは、他のプロセスがまだ処理されている行を取得できないようにするのに役立ちます。選択したショーでEXPLAINを行う

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

ただし、ログに2種類のエラー/警告が表示されることがあるため(5分ごと)、プロセスによって並列処理が高すぎるように見えます。

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)

私の質問は、これらのロックの問題を最小限に抑えるために、誰かが私を正しい方向に向けることができるかということです。(本番状態では、並列処理は現在の3〜4倍になるため、ロックの問題がはるかに増えると思います)

ヒントによるSELECTインデックスを使用するように変更しました。私の問題はもうlockwaitタイムアウトです(デッドロックが消えました)。crawlerUSE INDEX(crawler)

EXPLAIN現在のUSE INDEX()ショーで(テーブルに含まれるデータが増えたため、行数が増えました):

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
4

3 に答える 3

3

EXPLAIN レポートは、単一列インデックスのみを使用していることを示してproc_idおり、クエリは 60 万行を超える行を調べる必要があります。crawlerオプティマイザがインデックスを選択した方がよいでしょう。

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

解決策は、インデックスを使用して調べる行の範囲を狭めることです。これにより、行をより迅速に見つけることができるだけでなく、広範囲の行がロックされるのを回避できるようになります。ここcrawlerではインデックスが役立つはずですが、そのインデックスを使用していない理由はすぐにはわかりません。

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

もう 1 つのオプションは、インデックス ヒントを使用することです。

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

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


詳細な説明により、RDBMS を FIFO として使用していることは明らかです。これは、RDBMS の効率的な使用法ではありません。この目的のためのメッセージ キュー テクノロジがあります。

以下も参照してください。

于 2012-12-29T20:09:32.550 に答える
1

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

このような高度な並列作業を行っている場合、競合に基づく問題を軽減する最も簡単な方法の 1 つは、すべてのスレッドがどの行を処理する必要があるかを事前に知る方法を発明して、競合を完全に排除することです。その後、リソースを競合することなくロックでき、データベースは競合を解決する必要がありません。

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

一般に、NUM_THREADS がある場合、各スレッドはデータベースから THREAD_ID + i*NUM_THREADS の ID を選択し、それらで動作します。

スレッドが停止または終了する可能性があり、データベース内の行がまったく操作されない可能性があるという問題が発生しました。この問題にはいくつかの解決策があります。そのうちの 1 つは、ほとんどまたはすべてのスレッドが終了したら「クリーンアップ」を実行し、すべてのスレッドが可能な限り断片的に行を取得し、クロールされていない URL がなくなるまでそれらをクロールすることです。より洗練されたものになり、いくつかのクリーンアップ スレッドを常に実行したり、各スレッドに時々クリーンアップ作業を実行させたりすることができます。

于 2012-12-29T19:11:25.823 に答える
0

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

これにより、ロックに関連するすべての問題を軽減し、必要な数のプロセスを並行して実行できるようになると思います。

update ... limit 1PS: 明確にするために、 1 行だけを更新するように話しています。

編集: 解決策

以下に示すように正しいものです。

于 2012-12-31T15:16:28.363 に答える