MySQL:トランザクションとテーブルのロック


110

データベースの整合性を確保し、SELECTとUPDATEの同期を維持し、他の接続が干渉しないようにするために、トランザクションとテーブルのロックを少し混同しています。する必要がある:

SELECT * FROM table WHERE (...) LIMIT 1

if (condition passes) {
   // Update row I got from the select 
   UPDATE table SET column = "value" WHERE (...)

   ... other logic (including INSERT some data) ...
}

他のクエリが干渉してそれを実行しないようにする必要がありますSELECT(その接続が行の更新を完了する前に「古い値」を読み取ります)。

私はデフォルトLOCK TABLES tableで、一度に1つの接続のみがこれを実行していることを確認し、完了したらロックを解除することもできますが、それはやり過ぎのようです。トランザクションでそれをラップすることは同じことを行いますか(他の接続がまだ処理している間に他の接続が同じプロセスを試行しないことを保証します)?またはでしょうSELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODE良いかも?

回答:


173

テーブルをロックすると、他のDBユーザーが、ロックした行/テーブルに影響を与えることを防ぎます。ただし、ロック自体は、ロジックが一貫した状態で出力されることを保証するものではありません。

銀行システムについて考えてください。オンラインで請求書を支払う場合、トランザクションの影響を受けるアカウントが少なくとも2つあります。そして、お金が送金される受取人の口座。そして、銀行の口座には、トランザクションに請求されたすべてのサービス手数料が喜んで入金されます。銀行は非常に愚かであることを(誰もが最近知っているように)考えると、銀行のシステムは次のように機能するとします。

$balance = "GET BALANCE FROM your ACCOUNT";
if ($balance < $amount_being_paid) {
    charge_huge_overdraft_fees();
}
$balance = $balance - $amount_being paid;
UPDATE your ACCOUNT SET BALANCE = $balance;

$balance = "GET BALANCE FROM receiver ACCOUNT"
charge_insane_transaction_fee();
$balance = $balance + $amount_being_paid
UPDATE receiver ACCOUNT SET BALANCE = $balance

現在、ロックもトランザクションもないため、このシステムはさまざまな競合状態に対して脆弱です。その最大の原因は、アカウントで複数の支払いが実行されるか、レシーバーのアカウントで並行して実行されることです。コードで残高を取得し、huge_overdraft_fees()などを実行している間に、他の支払いが同じタイプのコードを並行して実行している可能性があります。彼らはあなたの残高(例えば$ 100)を取得し、取引を行い(あなたが支払っている$ 20とそれらがあなたをねじ込んでいる$ 30を取り出す)、そして今や両方のコードパスは2つの異なる残高を持っています:$ 80と70ドル。どれが最後に終了するかに応じて、最終的に$ 100-$ 20-$ 30になるはずの$ 50の代わりに、アカウントのこれらの2つの残高のいずれかになります。この場合、「あなたに有利な銀行エラー」

ここで、ロックを使用するとします。請求書の支払い($ 20)が最初にパイプに当たるので、それが勝ってアカウントレコードをロックします。これで独占使用権を得て、残高から$ 20を差し引いて新しい残高を安心して書き戻すことができます...そして、アカウントは予想どおり$ 80になります。しかし...ええと...あなたはレシーバーのアカウントを更新しようとします、そしてそれはロックされ、コードが許可するよりも長くロックされ、トランザクションがタイムアウトします...私たちは愚かな銀行を扱っているので、適切なエラーではなく処理すると、コードは単にを引き出し、exit()20ドルは電子のパフに消えます。これであなたは$ 20を使い果たしましたが、受信者に$ 20を支払う必要があり、あなたの電話は取り戻されます。

だから...トランザクションを入力してください。トランザクションを開始し、アカウントに$ 20を借方記入し、受信者に$ 20を入金しようとすると、何かが再び爆破します。しかし、今回はexit()、コードの代わりに、コードで実行できるのでrollback、$ 20がアカウントに魔法のように追加されます。

結局、それはこれに要約されます:

ロックは、処理しているデータベースレコードを他のユーザーが妨害しないようにします。トランザクションは、「後の」エラーが、実行した「以前の」ことを妨害しないようにします。どちらを使用しても、最終的に問題なく動作することを保証することはできません。しかし、一緒に、彼らはそうします。

明日のレッスン:デッドロックの喜び。


4
私も混乱しています。受け取り側のアカウントに100ドル入っているとします。このアカウントから20ドルの請求書支払いを追加します。トランザクションについての私の理解は、トランザクションが開始すると、トランザクション内の操作はデータベースをトランザクションの開始時の状態で見ることです。つまり、変更するまで、レシーバーアカウントは$ 100になります。つまり... $ 20を追加すると、実際には$ 120の残高が設定されます。しかし、トランザクション中に誰かがレシーバーアカウントを0ドルに引き出した場合はどうなりますか?これは何とか防止されていますか?彼らは魔法のように再び$ 120を手に入れますか?これがロックも必要な理由ですか?
Russ

はい、ここでロックが機能します。適切なシステムはレコードを書き込みロックし、トランザクションの進行中に他の誰もレコードを更新できないようにします。偏執狂的なシステムは、無条件のロックをレコードに課し、だれも「古い」バランスを読み取ることができないようにします。
マルクB

1
基本的に、トランザクションをコードパス内のセキュリティで保護されているものと見なします。ロックは、「並列」コードパス全体で安全なものをロックします。デッドロックが発生するまで...
マークB

1
@MarcB、それでは、トランザクションだけを使用してすでにロックが設定されていることが保証されているのに、なぜ明示的にロックを行わなければならないのですか?トランザクションだけでは不十分なため、明示的なロックを行わなければならない場合もあるでしょうか。
Pacerier 2014

2
この答えは正しくなく、間違った結論につながる可能性があります。このステートメント:「ロックにより、処理中のデータベースレコードが他のユーザーに干渉されるのを防ぎます。トランザクションにより、「後の」エラーが「以前の」作業に干渉するのを防ぎます。どちらでも、終わりです。しかし、一緒に、彼らはそうします。」-あなたはそれが非常に間違っていると愚かな参照の記事で、解雇になるだろうen.wikipedia.org/wiki/ACIDen.wikipedia.org/wiki/Isolation_(database_systems)dev.mysql.com/doc/refman/5.1/ ja /…
Nikola Svitlica

14

あなたが言ったように、トランザクション内SELECT ... FOR UPDATEまたはSELECT ... LOCK IN SHARE MODEトランザクションが必要です。通常SELECTは、トランザクション内かどうかに関係なく、テーブルをロックしないためです。どちらを選択するかは、トランザクションの進行中に他のトランザクションがその行を読み取れるようにするかどうかによって異なります。

http://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html

START TRANSACTION WITH CONSISTENT SNAPSHOT他のトランザクションが一緒に来てその行を変更する可能性があるため、あなたのためにトリックを行うことはありません。これは、下のリンクの一番上に記載されています。

他のセッションが同じテーブルを同時に更新する場合[...]データベースに存在しなかった状態のテーブルが表示される場合があります。

http://dev.mysql.com/doc/refman/5.0/en/innodb-consistent-read.html


7

トランザクションの概念とロックは異なります。ただし、トランザクションはロックを使用して、ACIDの原則に従うことを支援しました。テーブルを読み取り/書き込み中に他のユーザーが同時に読み取り/書き込みできないようにする場合は、これを行うためのロックが必要です。データの整合性と一貫性を確認したい場合は、トランザクションを使用することをお勧めします。ロックを伴うトランザクションにおける分離レベルの概念は混在していると思います。トランザクションの分離レベルを検索してください。SERIALIZEは必要なレベルでなければなりません。


これが正解です。ロックは競合状態を防ぐためのものであり、トランザクションは複数のテーブルを従属データで更新するためのものです。トランザクションがロックを使用しているにもかかわらず、2つのまったく異なる概念。
ブルーウォーター

6

複数のスレッドが同じテーブルを更新しているときにをIF NOT EXISTS ...実行してからを実行すると、同様の問題INSERTが発生しました。

私はここで問題の解決策を見つけました:標準SQLでINSERT IF NOT EXISTSクエリを記述する方法

これはあなたの質問に直接答えるものではありませんが、チェックと挿入を1つのステートメントとして実行するのと同じ原則が非常に役立ちます。更新を実行するように変更できるはずです。


2

ロックとトランザクションと混同されています。これらは、RMDBでは2つの異なるものです。トランザクションがデータの分離に焦点を当てている間、ロックは同時操作を防ぎます。説明と優雅な解決策については、この素晴らしい記事をチェックしてください。


1
ロックは、他のユーザーが作業中のレコードに干渉するのを防ぎ、それが何をするのかを簡潔に説明します。トランザクションは、後のエラー(他のユーザーが並行して変更を行うもの)が以前に行ったことに干渉するのを防ぎます(誰かが何かをした場合のロールバックを可能にします)並行して)トランザクションをかなりまとめます...これらのトピックの彼の理解について何が混乱していますか?
steviesama

1

私は

START TRANSACTION WITH CONSISTENT SNAPSHOT;

そもそも、

COMMIT;

最後に。

ストレージエンジンがトランザクション(InnoDB)をサポートしている場合、その間に行うことはすべて、データベースの他のユーザーから分離されます。


1
彼が選択したテーブルは、特にロックしない限り(またはUPDATEが発生するまで)、他のセッションにロックされません。
アリソンR.

MySQLのドキュメントでSTART TRANSACTION WITH CONSISTENT SNAPSHOTについて読んだ後、同じ行の更新から別の接続が実際にロックアウトされる場所がわかりません。私の理解では、トランザクションの開始時にテーブルが開始されたように見えます。したがって、別のトランザクションが進行中で、すでに行を取得しており、それを更新しようとしている場合、2番目のトランザクションは、更新される前の行を表示します。そのため、他のトランザクションが行おうとしているのと同じ行を更新しようとする可能性があります。それは正しいですか、それとも進捗状況に何か不足していますか?
ライアン、

1
@Ryanロックは行いません。あなたは正しいです。ロックの有無は、実行する操作の種類(SELECT / UPDATE / DELETE)によって決まります。
アリソンR.

4
そうですか。それはあなた自身のトランザクションの読み取りの一貫性を与えますが、あなたがそうする直前に他のユーザーが行を変更することを妨げません。
Martin Schapendonk 2010年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.