知っている必要がありますデータベースは次のようになります。ロックとの取引

EDITORIAL

この記事では、消化のために彼自身のレコードの後に​​作られたウェブ上の優れたブログの他の著者を確認することです。MySQLのInnoDBストレージエンジンで記事に基づいています。

オリジナルボーエン住所:ポイントI

ロック

ロックの知識の概要

私たちは、概要と簡単なフォローアップについてののロックを見てみましょう。

データベースのロック - 概要

一般的に、私たちのプログラムはまだ正常に実行することができます。これらのロックデータベースなので、暗黙の助けを借り、私たちは追加;だけで、特定のシナリオの下で、それは手動でロックにプログラマが必要です。

  • 「クエリ」SELECTを実行する前に、それは自動的にすべてのテーブルがに関与し、「テーブルレベルのロック」を追加与える読み取りロックを、「更新」UPDATEを実行する前に、DELETE、INSERT、自動的に関連するテーブルの追加「テーブルレベルへでロック」書き込みロック

  • 以下のためのInnoDB、およびインデックス使用の「行レベルのロックを」関与するその後InnoDBが「ロック・テーブル」「ロック・ライン」に変換され、自動的にデータセットに追加され、「更新」UPDATE、DELETE、INSERTステートメントにし排他ロック(X)

:のInnoDBのみだけそうでない場合は、「ロー・レベル・ロック」を用いてデータ「インデックス」を取得し、InnoDBのテーブルロックが使用される;である、InnoDBテーブルの行ロックベースのインデックス

特例

:私たちは、テーブルに列を追加する場合は手段は、「普通の指標」であるインデックス列の属性を繰り返してもよいです

一般的な指標については、リピート率 MySQLは、これにより、非インデックス付きSQLになりますインデックス、などの一般的なインデックスを入れていないだろう、テーブルロックを形成します

ロックカテゴリ

上記の図から、ビューのロック粒度のために、我々は、「テーブル・レベル・ロック」および「行レベルのロック」にそのロックを見ることができます

  • 「テーブルロック」:小さなオーバーヘッド、高速ロック、デッドロックが発生しない、ロックの粒度、ロック競合の可能性が高い、同時実行の最低度

  • 「ラインロック」:大型オーバーヘッド、スローロック、デッドロックが発生します。ロック小型、ロック競合の可能性が低い、高い同時実行

これと同時に、ストレージエンジンのサポートをロックする異なる粒度が同じではありません

  • MyISAMテーブルは、のみテーブルロックをサポートしています

  • InnoDBの行ロックとテーブル・ロック・サポート

我々の下には、「ロック・テーブル」と見てとる「行ロックを。」

テーブルロック

データベースの場合、2つのテーブルのロックがあります。「ロックテーブルの読み取り」Table Read Lockと「テーブルの書き込みロックが。」Table Write Lock

両方のロック彼らにとって、ブロッキング状況があります:

  • 読み取りはブロックされません:現在のユーザーをデータを読み取るために、他のユーザーがデータを正しく読み取ることができ、ロックしません

  • 書き込みブロック:データを読み込むため、現在のユーザーが、他のユーザーがデータを読み込んで、現在のユーザーを変更することはできません、それがロックされます

  • 書き込み障害物:データを修正するために、現在のユーザーは、他のユーザーがロックされるデータを変更している現在のユーザーを変更することはできません

次のようにそれらの間の非互換性があります:

表ロックの互換性

要約すると、テーブルレベルのロックのために:

  • 読み取りおよび書き込みロックを排他的

  • 読み取りおよび書き込み操作シリアル

行ロック

先に述べたように、行ロックは「InnoDBの」ユニークなロック、我々はMySQLの一般的には「InnoDBの」ストレージエンジンを使用することで使用しています。

一般的にはInnoDBを使用しているのはなぜ

InnoDBテーブルとMyISAMの2つの本質的な違いがあります。

  • InnoDBはサポート行ロックを

  • InnoDBはサポートして取引を

「ラインロック」と「トランザクションが」小さい粒度の同時実行制御に私たちを可能にする、正しい使用は、同時実行データベースを向上させることができます。

InnoDBは二つの「行ロック」以下をサポートしています。

  • 共有ロック

    また、共有されている「読み取りロック」または「S錠」として知られている、複数のトランザクションを同時に読み取ることができ、同じリソースを、しかし、他のトランザクションが変更することはできません

    換言すれば、同じ行に対して、同時に複数の読み取りロック(一度に読み出すことができるように)があってもよい;しかし、書き込みロックと読み取りロックは、相互に排他的で、両方がそう同じ時間を変更することから、他のトランザクションが許可されていない時間を読み出す(存在しないことができ)。

  • 排他ロック

    また、「書き込みロック」や「Xロック」として知られている排他的で、書き込みロックがされます他の書き込みロックと読み取りロックをブロックするので、他のトランザクションは、書き込みデータは、ロックが読み取りまたは書き込みを保持することができません。

さらに、行およびテーブルのロック、マルチ粒度ロック機構の共存を可能にするために、InnoDBのが意図的ロック(意図ロックの)内部使用の二種類を、両方の意図ロックされているテーブルロック

  • その意図は、ロックを共有IS:トランザクションがロックを共有データ行の行を追加しようとしていること、トランザクションの前にロックがテーブルの上にロックIS取得する必要があります共有データの行に。

  • テント排他ロックIX:トランザクションがデータ行排他ロックの行を追加しようとしていることを、彼は最初のテーブルのロックIXロックを取得する必要があります前に、データ行の行を追加する業務

意図的ロックの両方のために、MySQLは自動的に、我々が得るのを助けるません手動で取得する必要はありません


業務

「総務」には、我々は、このような銀行振込みなど事業運営の不可欠な部分としてそれを理解することができます。我々は本質的に、我々はの並行処理「業務」の能力を向上させるために望んでいる、MySQLの同時処理能力を向上させたいです。

本質的には、トランザクション分離レベルは、ロック機構によって達成するが、ロックの詳細を隠蔽します。

取引の特徴

トランザクションが適切に処理することができることを確実にするために、我々は、トランザクション特性(ACID)の次の4つの規定を持っている必要があります:

  • アトミックAtomic:各ステップの動作は不可分トランザクションである、一連の動作を確実に完了または終了していません。

  • 一貫性Consistency:前と後のトランザクションが完了され、データベースとビジネス・ルールが一貫していなければならない状態、例えば、Bアカウント各転送後に、両方の不変のトータルバランス。

  • アイソレーションIsolation:お互いの取引事業からの分離;これは、トランザクションが独立している必要があります、あなたはに依存しているか、どのような方法その他の取引に影響を与えてはならないことを示しています。

  • 永続性Durability:データ処理の最後にトランザクションを示し、データの変更は永続的でなければならず、それが成功するか、ロールバックトランザクションは、トランザクションログは永久的な取引を維持することができるかどうか。

トランザクション分離レベル

トランザクション分離レベルは、度を単離することが記載されており、より高いアイソレーションレベル、干渉他のものを受けにくく、同時性能悪化、より高いセキュリティを確保するための分離の比較的高いレベル。

次のように4つの一般的なトランザクション分離レベル、ローからハイへの分離の度合いは、があります。

  • 「READ UNCOMMITTED」:コミットされていない読んで、汚い読み込み、非反復可能読み取り、ファントム読み取りがあるでしょう

  • 「読み取りコミット」:リードコミッティ;が存在します非反復可能読み取り、ファントム読み取り

  • 「反復可能読み取り」:反復可能読み取り、マジック読書(ロックにギャップを持つMySQLのがあるだろうGPA回避ファントムリードへ)

  • 「シリアライズ」:シリアルは、起きているように見えませんが、他のトランザクションの実行順序後のこの時間はわずか1

観察を見つけることができ、高いトランザクション分離レベルの問題でも、低トランザクション分離レベルに表示されるのは、ローからハイグランド問題への異なる分離レベルの存在を分析してみましょう、その後、ケースは、より高いレベルを解決することを目的とする方法を見ていますこれらの問題。

デフォルトでは、「反復Reable」と「MySQLの」デフォルトの分離レベルは、「Oracleの」デフォルトの分離レベルは「読み取りコミット」です。

"READ UNCOMMITTED" と "ダーティリード。"

ダーティ読み取り:データを参照するには、別のコミットされていないトランザクションのトランザクションを読みます

例えば:A Bは、転送にトランザクションの実行の転送を意図したが、まだコミットしていない、Bがデータを読みながら、彼らは自身がバランスがあまり変わっアカウント見つけ、その後、B A通知は、「私はお金を受け取った」と述べた。仮説その後、実行しrollbackているBは、ビューの口座残高に再び操作、ロールトランザクションを、そして、あなたは見つけるでしょうお金とノー増加を

私たちは、データはには言及残高の増加に読んだの途中でBになる脏数据、あること、データが中間データの状態のみが最終的にクリーンかつ効果的ではない別のケースに変換することができるであり、これは読むかもしれません」ケースのダーティ「データは、我々が呼ばれるダーティリードを

なぜ「ダーティリードが。」があります

増加「共有ロック」上のデータは、データをロックする「排他ロック」を読み出すにつながることができますがない場合には、データの読み込み中にB。

私たちは、汚れたリード処理の一番上を見てみましょう。

  • B現在の残高がXであることを、彼らのバランスをチェック。

  • B.に50ターンを想定した(データはまだロックされている、プラス書き込みロックが、コミットしていなかった、)AとBの口座残高を修正し、ある転送実行、

  • 参照Bは、自身のバランスをとる、Aから求めることができる(これはAで作られたものを読み取り、読み取りロックを添加しない)転送を受け、現在の残高が読み出され、X + 50。

  • 転送前に回復する当時転写ロールトランザクション、AとBの口座残高

  • Bは、その残高を確認し、それらのバランスおよび変更バック(転送動作のロールバック、XになっBの残高)を見つけます。

私たちが「コミットされていない」を読まれる他のコミットされていないトランザクションからのデータを読み取りますが、この場合には見ることができますRead Uncommitted

他の例では、分離レベル、我々が発生ダーティリードを避けることができます。あなたは回避する方法を学ぶ前に、しかし、我々は、データベースを理解する必要がある「MVCCを。」

MVCC(マルチバージョン同時実行制御):マルチバージョン同時実行制御

上記の問題は本質的には、読んで、同時データベースへの書き込み操作が読み取られるように一貫性のないデータであってもよい読むことです、「ダーティ・リード」。

これを避けるために、あなたは同時アクセス制御データベースを必要とする、最も簡単な方法は、にある「ロックアクセス。」以来、ロックのシリアル読み取りおよび書き込み操作は、したがって、矛盾した状態を表示されませんでしょう。しかし、読み出し動作が劇的に読み取り性能が低下する、書き込みをブロックされます

Javaの同時パッケージでは、読み取り状況を最適化するために設計されたクラスの「コピーオンライト」シリーズは、書き込みよりもはるかに大きいがされています。そして、その最適化のための手段は、書き込み動作時に、データをコピーし、修正した後、元のデータには影響を与えません編集した後、原子スワップ古いデータを交換し、読まれるだけで、元のデータを読み込み、 。これにより達成される読み出し動作をブロックしない書き込み最適化読み取り効率。操作は、相互に排他的との書き込みにあり、各書き込み操作は、コピーを持って、それは書き込みよりも大きな読み取るためにのみ適して

MVCCは何ですか

「MVCC」の原則と「コピーオンライト」に類似:

MVCCの契約に基づき、ため各生成されます「読み」スナップショットの一貫性をsnapshot、そして一定レベル(文レベルまたはトランザクション・レベル)読取り一貫性を提供するために、このスナップショットを使用します。ユーザーの観点からは、データベースは、同じデータの複数のバージョンを提供できるようです。あなたは非ブロッキング読み取りを実現することができます。MVCCは、以下のように考えることができる行レベルロックのアップグレード版

MVCCは、データが複数のバージョンがあり、このバージョンでは、タイムスタンプに基づいて、またはグローバル・トランザクションIDをインクリメントすることができますことができ、同時に、我々は別のトランザクション・データを参照することは異なっています。

連絡先の前述の「コピーオンライト」と「MVCC、」私たちがすることができますどのように「ダーティ読み取り」解決への質問、それ?

このように、トランザクションのコミット前に、読み取り、後同様に、我々はデータが変更されたときに、その変更が(つまり、トランザクションがコミットされる)、その後、古い原子データとそのバージョン番号を交換完了しているデータのコピーを作成することができますデータは常に、およびトランザクションのコミット後に、我々は変更が完了した後に、新しいデータ(更新されたバージョンを読むことができます(データのコピーの変更があるため)、私たちはそれぞれの最新バージョンを読むことができると同じですデータ)。これは「もあるコミット読むソリューション「ダーティリードを」」。

「リードコミッティ」と「非反復可能読み取り」

すでに「読み取りコミット」方法を説明したように汚れを避けるために、読み取ります

唯一のトランザクションでcommitのみ、データを書き込むと、バージョン番号を更新します。常に読み込みデータの最新版を読んで

だから、プロセスへのアクセスを同時トランザクションの「読み取りコミット」我々は転送の例に従って再度行く、非常に明確です:

  • B現在の残高がXであることを、彼らのバランスをチェック。

  • B.に50ターンを想定した(データはまだロックされている、プラス書き込みロックが、コミットしていなかった、)AとBの口座残高を修正し、ある転送実行、

  • Bは、その残高を確認変わらずバランスをとるために読んで(トランザクションがまだ提出していないため、B)のxとして、。

  • Aは、転送トランザクションを提出し、更新されたバージョンのB残高。

  • B残高を表示、あなたは、x + 50の独自のバランス(X + 50の最新バージョンのバランス)を見つけることができます。

「ダーティリード」の問題を解決するが、それは、対応する問題がある、まだある「読み取りコミット」。

非反復可能読み取り

「非反復可能読み取り」、名前が示すように、ある一貫性のあるデータを読んで複製することはありませんBは、トランザクションに関係なく、現在のトランザクションBの終わりを持っていない、他のトランザクションは、Bは意味をなさないことを前に得られたデータをB A関連のクエリデータを変更することができます。

前回の転送の場合には、クエリでは、B 2倍の残高照会トランザクションが、他のトランザクションの変更は一貫性のない結果につながるので、Bは、トランザクションは「非反復可能読み取り」の問題が登場です。

なぜ「非反復可能読み取り」があります

読むの現在のデータベース上のデータにする文の読み取りロックを取得し、しかし、読んだ後、それがロック解除にかかわらず、現在のトランザクションのは、終了していないので、非反復可能読み取り、その結果、読まれたデータを変更するには、このトランザクションの他の業務を可能にします。

したがって、我々が言うように、「読むには、コミット」文レベルのスナップショット(データのみを読んで、プラスの文がロックを読み取るために)。データベース分離レベル「反復可能読み取り」のより高いレベルでは、この問題を回避することができます。

「反復可能読み取り」と「幻をお読みください。」

上記といえば、私たちは同じトランザクション内のデータを読みたいの変更その他の事項に影響を与えていない、そして前のスナップショットが思うsnapshot、私たちはする必要があるときに、トランザクションがオープンスナップショットのバージョン番号を保存し、このトランザクションの時間その上のデータの対応バージョンを読み込みます。

実際には、「反復可能読み取り」であり、ロック列関与し、ロックをトランザクションの終了が解除されていないまで、「それは表示されません、我々は他の事項を運営されていないこのトランザクションでデータを変更することができることを保証することができるように、非反復は、の状況」をお読みください。

しかし、また、そのデータの暗号化ロックの取引のために保有されているレベルであるので、「繰り返し読むには」と呼ばれ、トランザクション・レベルのスナップショット

ここではInnoDB内でMVCCを実装する方法のボーエン抜粋抜粋:

InnoDBの-MVCC-1

InnoDBの-MVCC-2

マジック読書

最後に、私たちがする、「反復可能読み取り」を知っている必要があり、データラインに関連するロック、これはまた、データのセクションA操作でトランザクションテーブルの場合には、他のトランザクションがまだテーブルに新しいデータを送信することができ、それを意味しています同じような錯覚を持っていたとして、結果は、トランザクション二回統計的矛盾につながる、これは「ファントム読み取り」です。

2「ファントム読み取り」を示すためにレッツ・使用例:

トランザクションが存在しないレコードの挿入時にチェックされている、データが意外に検出されたデータの存在は、幻覚などの一般的以前に取得したことが判明しました。

二つのパネルAおよびBを想定、開封後、それらはユーザ照会テーブルである含んでいるid = 1ユーザ(ユーザ・テーブルのプライマリ・キーにIDを):

-- A 和 B同时执行
SELECT * FROM user WHERE id = 1;

这里假设 user 表为空,因此这里 A 和 B 都查询不到这个用户的存在,所以 A 和 B 都插入一个id = 1的用户数据到 user 表中,我们假设 A 事务先执行,B 事务后执行:

-- A 先执行,B 后执行
INSERT INTO user(id, name) VALUE(1, '张三');

那么在顺序执行的时候,很明显,A 能够成功插入,但 B 插入的时候会产生主键冲突,我们宏观来看是可以理解的,因为 A 已经插入了id = 1的数据;但对于 B 来说,它之前的SELECT语句明明表示不存在,但现在插入的时候却提示已经存在id = 1的数据了,仿佛之前的查询结果是幻觉一样,这就称为「幻读」。

二、同样的条件下,第1次和第2次读出来的表记录数不一样。

假设当前工资为 1000 的有 10 人,事务 1 读取当前工资为 1000 的员工数目,得出结果10条:

-- 结果为 10
SELECT COUNT(*) FROM employee WHERE salary = 1000;

这时事务 2 向员工表中插入一个工资为 1000 的员工信息,并提交:

INSERT INTO employee(name, salary) VALUE('newEmployee', 1000);
COMMIT;

这时事务 2 再次读取员工数目,会得出结果为 11:

-- 结果为 11
SELECT COUNT(*) FROM employee WHERE salary = 1000;

这也就是出现了「幻读」。

怎么解决「幻读」

最简单的解决方式便是让事务「串行」执行,每次同时只能执行一个事务,但这种情况下,我们就无法并行操作事务了。而在「Repeatable Read」的情况下,我们可以给读取的数据加上间隙锁GAP来处理幻读的情况。

间隙锁 GAP

当我们用范围条件检索数据而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合范围条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做「间隙(GAP)」。InnoDB也会对这个间隙加锁,这种锁机制就是所谓的「间隙锁」。

例子:假如emp表中只有101条记录,其empid的值分别是1,2,...,100,101:

SELECT * FROM  emp WHERE empid > 100 for UPDATE;

上面是一个范围查询,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的「间隙」加锁

InnoDB使用间隙锁的目的有两个:

  • 为了防止幻读(上面也说了,「Repeatable Read」隔离级别下再通过GAP锁即可避免了幻读)

  • 满足恢复和复制的需要

    MySQL的恢复机制要求:在一个事务未提交前,其他并发事务不能插入满足其锁定条件的任何记录,也就是不允许出现幻读

值得注意的是:间隙锁只会在「Repeatable Read」隔离级别下使用

乐观锁和悲观锁

我们可以发现,之前的数据库隔离级别,都是为了解决读写冲突问题,我们在默认的「Repeatable Read」的情况下考虑一个问题:

図の更新例を失いました。

上面我们假设,T1~T2时间,张三完成一个查询事务;T3~T6,李四完成一个更新事务;在后序时间段,张三基于上一次的查询进行一个更新事务。在这种情况下,李四的更新就被张三的更新覆盖了,也就是发生了更新丢失

我们可以通过以下方法解决这个问题:

  • 使用Serializable隔离级别,这时事务是串行执行的

  • 乐观锁

  • 悲观锁

乐观锁

「乐观锁」是一种思想,不是数据库层面上的锁,是需要自己手动去加的锁。具体实现是,表中有一个版本字段,第一次读的时候,获取到这个字段。处理完业务逻辑开始更新的时候,需要再次查看该字段的值是否和第一次的一样;如果一样,则更新,并同步更新版本字段;反之拒绝。

之所以叫乐观,因为这个模式没有从数据库加锁,等到更新的时候再判断是否可以更新

具体过程是这样的:

张三select * from table--->会查询出记录出来,同时会有一个version字段

IMG

李四select * from table--->会查询出记录出来,同时会有一个version字段

IMG

李四对这条记录做修改:update A set Name=lisi,version=version+1 where ID=#{id} and version=#{version},判断之前查询到的version与现在的数据的version进行比较,同时会更新version字段

此时数据库记录如下:

IMG

张三也对这条记录修改:update A set Name=lisi,version=version+1 where ID=#{id} and version=#{version},但失败了!因为当前数据库中的版本跟查询出来的版本不一致

IMG

悲观锁

「悲观锁」是数据库层面加锁,所有更新操作都会阻塞去等待锁。

我们在语句后加FOR UPDATE,即可使用悲观锁:

SELECT * FROM xxx FOR UPDATE

FOR UPDATE等于对选中的行加了「排它锁」,加了写锁之后,其他事务就不能对它进行访问和修改了,这也就保证了不会出现更新丢失的情况。

如何选择乐观锁或悲观锁

两种锁各有优缺点,不可认为一种好于另一种,像「乐观锁」适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。

死锁

并发的问题就少不了死锁,在MySQL中同样会存在死锁的问题。

但一般来说MySQL通过回滚帮我们解决了不少死锁的问题了,但死锁是无法完全避免的,可以通过以下的经验参考,来尽可能少遇到死锁:

  • 固定的顺序访问表和行。比如对两个job批量更新的情形,简单方法是对id列表先排序,后执行,这样就避免了交叉等待锁的情形;将两个事务的sql顺序调整为一致,也能避免死锁。

  • 大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小。

  • 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率。

  • 降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为gap锁造成的死锁。

  • 为表添加合理的索引。可以看到如果不走索引将会为表的每一行记录添加上锁,死锁的概率大大增大。

总结

上面说了一大堆关于MySQL数据库锁的东西,现在来简单总结一下。

表锁其实我们程序员是很少关心它的:

  • 在MyISAM存储引擎中,当执行SQL语句的时候是自动加的。

  • 在InnoDB存储引擎中,如果没有使用索引,表锁也是自动加的。

现在我们大多数使用MySQL都是使用InnoDB,InnoDB支持行锁:

  • 共享锁--读锁--S锁

  • 排它锁--写锁--X锁

在默认的情况下,select不加任何行锁的。我们可以通过以下语句显示给记录集加共享锁或排他锁。

  • 共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE

  • 排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE

InnoDB基于行锁还实现了MVCC多版本并发控制,MVCC在隔离级别下的Read committedRepeatable read下工作。MVCC能够实现读写不阻塞

InnoDBは達成Repeatable readファントム読み取りロックを回避してきたギャップGAPとの分離レベルを。

悲観的ロックと楽観的ロックのために、私たちは次のように要約しています:

  • 状況は、データがロックダウンされ更新されないことを私たちは何かを間違っを見つけた場合、それは(ロールバック)を更新しません:オプティミスティック・ロックは、その名前が示すように、実際にアイデアです。多くの場合、達成するためにデータベースのバージョンフィールドを追加します。

  • 悲観的ロックは、データベースの同時実行の競合が発生するデータベース上の行ロックであるとデータを直接ロックを入れ、他のトランザクションは、現在のトランザクションが提出されるまで変更することができません。

おすすめ

転載: www.cnblogs.com/Bylight/p/11850761.html