1. 概要
マルチバージョン同時実行制御 (MVCC) は、同時実行制御テクノロジです。データベース同時実行制御を実装する MVVC は、UNDO ログ内のバージョン チェーンから切り離すことができません。これは、データ行の複数のバージョンを維持することによって実現されます。
簡単に言うと、現在のトランザクションは、別のトランザクションによって変更されている行をクエリします (この時点で読み取られるとダーティ リードが発生します)。ロックして待機する代わりに、データの履歴バージョンが読み取られます。応答時間が短縮されます。
MVVC は、Undo ログと Read View という 2 つのテクノロジによって実装されています。
2. スナップショット読み取りと現在の読み取り
MySQL InnoDB での MVCC の実装は、主にデータベースの同時パフォーマンスを向上させ、読み取り/書き込みの競合を処理するより良い方法を使用することを目的としています。読み取りと書き込みの競合がある場合でも、ロックとノンブロッキングの同時読み取りは実現できず、この読み取りは現在の読み取りではなくスナップショット読み取りを指します。現在の読み取りは実際にはロック操作です。
1. 現在の読み取り値
読み取られたレコードが最新のデータであることを確認し、他の同時トランザクションによってレコードが変更されるのを防ぐために、読み取り時にレコードをロックする必要があります。
ロックされた読み取りは現在の読み取りと呼ばれ、データの追加、削除、または変更には最初にデータを読み取る必要があります。この読み取りプロセスも現在の読み取りです。
SELECT * FROM t LOCK IN SHARE MODE; # 共享锁
SELECT * FROM t FOR UPDATE; # 排他锁
UPDATE SET t.. ログイン後にコピー
2. スナップショット読み取り スナップショット読み取りは一貫性読み取りとも呼ばれ、データ行のスナップショット バージョンを読み取ります。 MySQL では、通常の select ステートメント (共有モードでの for update または lock のない select ステートメント) は、デフォルトでロックなしでスナップショット読み取りを使用します。
SELECT * FROM table WHERE ... ログイン後にコピー
その理由は、スナップショットの読み取りによってロック操作が回避され、オーバーヘッドが軽減されるためです。
トランザクションの分離レベルがシリアルの場合、スナップショット読み取りは役に立たず、現在の読み取りに低下します。
3. 分離レベルとバージョン チェーンのレビュー 分離レベル:
MySQL のデフォルトの分離レベルは反復読み取り RR であり、非反復読み取りの問題を解決できます。 MySQL では、ファントム読み取りの問題を解決するための特別なサポートが提供されています。
ファントム・リーディングの問題はどのように解決されるのでしょうか?
# 問題を解決するには、ギャップ ロックと一時的なキー ロックを使用します。つまり、ロックがかかっています。この間、他のトランザクションはデータを挿入できません
MVCC メソッドはロックを必要とせず、消費電力が低くなります (欠点は、ファントム読み取りの問題を完全に解決できないことです)。
元に戻すログ バージョン チェーン:
InnoDB に対応して、クラスター化インデックスの各レコードには 2 つの必要な隠しフィールドが含まれます:
trx_id: トランザクションがクラスター化インデックス レコードを変更するたびに、トランザクションのトランザクション ID が trx_id 非表示列に割り当てられます。
roll_pointer: ロールバック ポインター。データが変更されるたびに、古いデータが元に戻すログに書き込まれます。新しいデータは古いデータを指し、バージョン チェーンを形成します。ポインタフィールドはロールバックポインタと呼ばれ、変更前のデータを見つけることができます。
#例: ID 8 のトランザクションがデータを作成すると、レコードの概略図は次のようになります。
ID 10 と 20 の 2 つのトランザクションがこのレコードを更新すると仮定します。プロセスは次のとおりです:
トランザクション 10事20 ##BEGIN; BEGIN; UPDATE 学生 SET name='李思' WHERE id=1; UPDATE 学生 SET name='王五' WHERE id=1; ##COMMIT; # #学生セットを更新name='Zhao Liu' WHERE id=1; 学生セットを更新 name='Qian Qi' WHERE id=1; # ##### ######専念;############每个修改都会生成一个undo log日志,并与其他日志相互链接形成版本链,从而呈现出该条数据的图示
每个版本中还包含生成该版本时对应的事务id 。
四、Read View 有了undo log就可以读取到记录的历史版本,那么在什么情况下,读取哪个版本的记录呢?这就用到了Read View,它帮我们解决了行的可见性问题。
读视图是指在使用MVCC机制进行快照读操作时产生的事务视图。这个视图是对当前数据库中所有活跃的、尚未提交的事务ID列表进行拍照的。
1.实现原理 四种隔离级别里,读未提交和串行化是不会使用MVVC的,因为读未提交直接读取某个数据的最新数据即可,串行化是通过加锁来读的。
读已提交和可重复读都必须保证读到的数据都是其他事务提交了的,所以,其他事务修改了数据但是还未提交,我们不能够访问该数据,但可以通过MVVC机制读取该记录的历史版本,核心问题就是需要判断版本链中的哪条历史版本是当前事务可见的,这也是ReadView要解决的问题。
Read View包含4个比较重要的内容:
当事务对表中的记录作出修改时,才会分配一个事务ID,否则如果事务仅进行读取操作,则该事务的ID默认为0。
trx_ids:表示在生成Read View时当前系统中活跃的事务id列表。提交了的事务不在其中。
up_limit_id:活跃的事务中最小的事务id。
low_limit_id:表示生成Read View时系统应该分配给下一个事务的id值,同样也表示系统中最大的事务id值。
请注意,低限制事务ID不一定是trx_ids中的最大值,因为事务ID是按递增顺序分配的。例如,有三个事务的id分别为1、2、5,其中id为5的事务提交了。那么一个新的读事务在生成ReadView时, trx_ids就包括1和2,up_limit_id的值就是1,low_limit_id的值就是6。
2.Read View规则
版本链
当某个事务有了Read View,访问某条记录时,需要按照下面的步骤判断该记录的哪个版本可见:
如果该版本记录的trx_id和Read View的creator_trx_id相同,意味着该版本的记录是由当前事务修改的,因此该版本可以被当前事务访问
如果该版本记录的trx_id小于Read View的up_limit_id,证明当前事务生成Read View时,此事务已经提交了,所以当前事务可以读取该版本。
如果该版本的trx_id大于等于low_limit_id,证明生成该版本的事务在当前事务生成Read View之后才开启,所以该版本不可以被当前事务访问。
如果被访问版本的trx_id属性值在ReadView的up_limit_id和low_limit_id之间,那就需要判断一下trx_id属性值是不是在trx_ids列表中,如果不在的话才能访问,否则不能访问。
3.整体流程 了解了这些概念之后,我们来看下当查询一条记录的时候,系统如何通过MVCC找到它:
在隔离级别为读已提交时,一个事务中的每一次SELECT查询都会重新获取一次Read View,而可重复读是第一SELECT操作才会生成Read View,之后的查询操作复用这一个。
导致这两种的差距是因为:可重复读要保证一个事务中相同的SELECT读取的内容是相同的。
五、举例 1.READ COMMITTED隔离级别下
现在有两个事务id分别为10、20的事务在执行:
-- id为10的事务
begin;
update t set name='李四' where id=1;
update t set name='王五' where id=1;
-- id为20的事务
更新其他行的数据 ログイン後にコピー
此刻,表中id为1的记录得到的版本链表如下所示:
此时新来一个事务执行如下操作:
begin;
select * from t where id=1;
-- 事务10、20未提交 ログイン後にコピー
查询到的结果为张三。
具体的过程如下:
在执行select语句前,先生成一个Read View,Read View的creator_trx_id为0,trx_ids列表的内容是[10,20],up_limit_id为10,low_limit_id为21。
查询name为王五的最新版本的记录,按规则进行对比,因为trx_id为10,10刚好是trx_ids中的记录,所以这条记录对当前事务不可见,根据回滚指针得到下一个版本
下一个版本name为李四,也不行
继续找到name为张三的版本,trx_id为8,8小于up_limit_id,所以该版本对当前事务可见,得到最终结果
接下来,再将id为10的事务进行commit提交。然后id为20的事务来更新记录:
begin;
-- id为20的事务
update t set name='赵六' where id=1;
update t set name='钱七' where id=1; ログイン後にコピー
此时版本链更新为:
再到刚才使用READ COMMITTED隔离级别的事务中继续查找这个id 为1的记录,得到的结果为name=王五的那条记录。执行过程如下:
生成Read View,Read View的creator_trx_id为0,trx_ids列表的内容是[20],up_limit_id为20,low_limit_id为21。
因为前两个版本的记录trx_id为20,存在trx_ids中,所以跳过
到第三条记录时,trx_id为10,小于20,可以读取,所以最终结果为王五
注意:READ COMMITTED,每次读取数据前都生成一个新的ReadView。
2.REPEATABLE READ隔离级别下 假如此时id为10的事务和id为20的事务正在修改,都未提交,修改内容和前面的一样,但是还未提交,此时当前事务做一个查询。
步骤为:
此时,id为10的记录提交事务。
当前事务又需要select id为1的记录,步骤为:
注意:REPEATABLE READ,每次读取都复用第一次生成的Read View
3.如何解决幻读 假设现在有一条数据,id为1
当前活跃的事务有10和20。
此时当前事务启动了,执行如下SQL语句:
begin;
select * from student where id>=1; ログイン後にコピー
在开始前生成Read View,内容如下:creator_trx_id=0,trx_ids= [10,20] , up_limit_id=10, low_limit_id=21。
由于id大于等于1的数据只有一个,且该数据的trx_id为8,小于up_limit_id,所以可以读取到。
在这之后id为10的事务新增了一行数据,增加了id为2的数据,且提交了。
此时当前线程继续查找id>=1的数据,因为是可重复读,复用刚刚的Read View。
得到两行数据,但是因为id为2的数据trx_id为10,该值在Read View的trx_ids中存在,所以该记录对当前事务不可见,所以最后查询到的数据只有一条记录。
如果当前事务再插入id为2的数据就插不进去,所以说MVVC只解决了一半的幻读问题。
以上がMySQL MVVC マルチバージョン同時実行制御を実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
2024-10-22 09:46:29
2024-10-13 13:53:41
2024-10-12 12:15:51
2024-10-11 22:47:31
2024-10-11 19:36:51
2024-10-11 15:50:41
2024-10-11 15:07:41
2024-10-11 14:21:21
2024-10-11 12:59:11
2024-10-11 12:17:31