ここでいくつかの質問があります。
1) 他のコマンドで中断できないトランザクションでインクリメントを実行できないのはなぜですか?
最初に、Redis の「トランザクション」は、ほとんどの人が従来の DBMS でのトランザクションと考えているものとは完全に異なることに注意してください。
# Does not work
redis.multi()
current = redis.get('powerlevel')
redis.set('powerlevel', current + 1)
redis.exec()
サーバー側 (Redis) で実行されるものと、クライアント側 (スクリプト) で実行されるものを理解する必要があります。上記のコードでは、GET と SET コマンドは Redis 側で実行されますが、 current への代入と current + 1 の計算はクライアント側で実行されることになっています。
原子性を保証するために、MULTI/EXEC ブロックは実行まで Redis コマンドの実行を遅らせます。そのため、クライアントは GET コマンドと SET コマンドをメモリに積み上げ、最後に 1 回でアトミックに実行します。もちろん、GET とインクリメントの結果に current を割り当てようとする試みは、かなり前に発生します。実際には、redis.get メソッドは文字列 "QUEUED" を返すだけで、コマンドが遅延していることを通知し、インクリメントは機能しません。
MULTI/EXEC ブロックでは、ブロックの開始前にパラメータを完全に把握できるコマンドのみを使用できます。詳細については、ドキュメントを参照してください。
2) トランザクションを開始する前に、代わりに反復して誰も値を変更しないまで待つ必要があるのはなぜですか?
これは同時楽観パターンの例です。
WATCH/MULTI/EXEC を使用しないと、競合状態になる可能性があります。
# Initial arbitrary value
powerlevel = 10
session A: GET powerlevel -> 10
session B: GET powerlevel -> 10
session A: current = 10 + 1
session B: current = 10 + 1
session A: SET powerlevel 11
session B: SET powerlevel 11
# In the end we have 11 instead of 12 -> wrong
WATCH/MULTI/EXEC ブロックを追加してみましょう。WATCH 句を使用すると、値が変更されていない場合にのみ、MULTI と EXEC の間のコマンドが実行されます。
# Initial arbitrary value
powerlevel = 10
session A: WATCH powerlevel
session B: WATCH powerlevel
session A: GET powerlevel -> 10
session B: GET powerlevel -> 10
session A: current = 10 + 1
session B: current = 10 + 1
session A: MULTI
session B: MULTI
session A: SET powerlevel 11 -> QUEUED
session B: SET powerlevel 11 -> QUEUED
session A: EXEC -> success! powerlevel is now 11
session B: EXEC -> failure, because powerlevel has changed and was watched
# In the end, we have 11, and session B knows it has to attempt the transaction again
# Hopefully, it will work fine this time.
そのため、誰も値を変更しないまで待つために反復する必要はありませんが、Redis が値が一貫していることを確認し、成功したことを示すまで、操作を何度も試行する必要があります。
ほとんどの場合、「トランザクション」が十分に高速で、競合が発生する可能性が低い場合、更新は非常に効率的です。ここで、競合が発生した場合、いくつかの「トランザクション」に対して追加の操作を実行する必要があります (反復と再試行のため)。ただし、データは常に一貫性があり、ロックは必要ありません。