3

新規ユーザー登録の一環として。事前にコンパイルされたリスト (テーブル) からリソース (この場合は Solr コア) を割り当てます。

5 人のユーザーがサインアップする場合、5 つの異なるコアを割り当てる必要があります。ユーザーが正常に登録された場合、割り当ては最終的なものになります (以下の図を参照してください)。

しかし、実際には、新規ユーザーを同時に登録すると、異なる行を選択するのではなく、同じ行を求めて競合します。X が登録するのに 5 秒かかる場合、X の「期間」内にある Y と Z の登録は、X によって同じ行を求めて競合するため失敗します。

質問: 1 秒間に 100 件のサインアップなどの高い同時実行性がある場合でも、トランザクションを競合なしで選択するにはどうすればよいですか?

table: User
user_id   name  core   
      1    Amy h1-c1
      2    Anu h1-c1
      3    Raj h1-c1
      4    Ron h1-c2
      5    Jon h1-c2

table: FreeCoreSlots
core_id  core status   
      1 h1-c1   used
      2 h1-c1   used
      3 h1-c1   used
      4 h1-c2   used
      5 h1-c2   used #these went to above users already
      6 h1-c2   free
      7 h1-c2   free
      8 h1-c2   free
      9 h1-c2   free

ものが分離された場合の疑似コード:

sql = SQLTransaction()
core_details = sql.get("select * from FreeCoreSlots limit 1")
sql.execute("update FreeCoreSlots set status = 'used' where id = {id}".format(
   id = core_details["id"]))
sql.execute("insert into users (name,core) values ({name},{core})".format(
   name = name,
   id   = core_details["id"]))
sql.commit()

1 秒間に 100 件のサインアップが発生すると、最初の行をめぐって競合し、重大FreeCoreSlotsな失敗を引き起こします。

解決策としてテーブル内のすべての行をロックする InnoDB SELECT ... FOR UPDATE ステートメントのように select... for update がありますが、分離を下げることを提案しているようです。この方法は正しい方法ですか?

4

3 に答える 3

5

トランザクション分離レベルをシリアル化可能に設定する。

http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html

事実上、これにより、トランザクション処理中に他のトランザクションが更新されたテーブルを変更するのをブロックし、データがアトミックで一貫した方法で更新されるようにします。

于 2012-10-01T12:07:09.440 に答える
2

私が尋ねる質問は、ユーザーが完了するのになぜ 5 秒かかるのかということです。START TRANSACTIONと の間はCOMMITほんの数分の 1 秒である必要があります。

同じ行を別の機会に同じ使用に割り当てることを防ぐには、 をFreeCoreSlots使用する必要がありますSELECT for UPDATE。私の意見では、ロックレベルは実際には問題ではありません。次の空き行をデータベースに設計する方法FreeCoreSlotsは、実際にはトランザクションが完了するまでロックされます。以下の私のテスト結果をご覧ください。そして、1 秒あたり 100 人の新規ユーザーの場合でも、それでも十分だと思います。しかし、これを克服したい場合でも、別の次の空き行をロックする方法を見つける必要がありますFreeCoreSlotsすべてのユーザーに。残念ながら、「ロックされていない場合は最初の行を選択する」という機能はありません。代わりに、ランダムまたはモジュラス ロジックを使用することもできます。しかし、すでに述べたように、毎秒 100 人の新規ユーザーという想像を絶する量であっても、それは問題ではないと思います。これで間違いがあれば、遠慮なくコメントを残してください。もう一度見てみたいと思います。

これが私のテスト結果です。デフォルトの InnoDB ロック レベル:なしで繰り返し読み取りFOR UPDATE。この方法では機能しません。

User 1:
    START TRANSACTION
    SELECT * FROM FreeCoreSlots WHERE status = 'FREE' LIMIT 1 -- returns id 1
User 2:
    START TRANSACTION
    SELECT * FROM FreeCoreSlots WHERE status = 'FREE' LIMIT 1 -- also returns id 1 !!!
User 1:
    UPDATE FreeCoreSlots SET status = 'USED' where ID = 1;
User 2:
    UPDATE FreeCoreSlots SET status = 'USED' where ID = 1; -- WAITS !!!
User 1:
    INSERT INTO user VALUES (...
    COMMIT;
USER 2:
    wait ends and updates also ID = 1 which is WRONG

ロック レベルの繰り返し読み取り可能ですが、FOR UPDATE. このように動作します。

User 1:
    START TRANSACTION
    SELECT * FROM FreeCoreSlots WHERE status = 'FREE' LIMIT 1 FOR UPDATE -- returns id 1
User 2:
    START TRANSACTION
    SELECT * FROM FreeCoreSlots WHERE status = 'FREE' LIMIT 1 FOR UPDATE -- WAITS
User 1:
    UPDATE FreeCoreSlots SET status = 'USED' where ID = 1;
User 2:
    still waits
User 1:
    INSERT INTO user VALUES (...
    COMMIT;
USER 2:
    Gets back Id 2 from the select as the next free Id
于 2012-10-06T10:43:49.943 に答える
0

可能な限り最後の瞬間まで割り当てを延期します。登録プロセス中にバインドしようとしないでください。

ステータス列を並行戦術として活用します。行がまだ「空いている」場合にのみ行を更新することを保証する述語を適用します。デフォルトの分離レベルでは、読み取りはノンブロッキングになります。更新により、1 行が更新されるか、まったく更新されません。成功しなかった場合はループし、試行回数に制限を設けることができます (空きスロットが使い果たされた場合)。複数の無料 ID をプリフェッチして、試行の往復を節約することもできます。最後に、これは、新しく割り当てられたスロットの ID を返すストアド プロシージャの方が適しています。その場合、ストアド プロシージャで同様の戦術を採用し、更新が成功するまでフェッチ/更新を行います。競合の多い環境では、シリアル割り当てがそれほど重要でない場合、上位 N 個の空いている行からランダムに行を選択しました。

sql = SQLTransaction()

core_details = sql.get("select * from FreeCoreSlots where status = 'free' limit 1")
sql.execute("update FreeCoreSlots set status = 'used' where id = {id} and status = 'free'".format(
   id = core_details["id"]))

//CHECK ROW COUNT HERE (I don't know what language or library you are using.
//Perhaps sql.execute returns the number of rows affected).
//REPEAT IF ROW COUNT < 1

sql.execute("insert into users (name,core) values ({name},{core})".format(
   name = name,
   id   = core_details["id"]))
sql.commit()
于 2012-10-07T14:50:28.367 に答える