Maison > Article > base de données > Analyse de l'instance MVCC de contrôle de concurrence multiversion MySQL
MVCC (Multiversion Concurrency Control), contrôle de concurrence multi-version
. Comme son nom l'indique, MVCC implémente le contrôle de concurrence
de la base de données via une gestion de plusieurs versions des lignes de données. Cette technologie garantit que les opérations de lecture cohérente
sont effectuées sous le niveau d'isolation des transactions d'InnoDB. En d'autres termes, il s'agit d'interroger certaines lignes qui sont mises à jour par une autre transaction, et vous pouvez voir les valeurs avant qu'elles ne soient mises à jour, afin que vous n'ayez pas à attendre qu'une autre transaction libère le verrou lors de la requête. . MVCC (Multiversion Concurrency Control),多版本并发控制
。顾名思义,MVCC是通过数据行的多个版本管理来实现数据库的并发控制
。这项技术使得在InnoDB的事务隔离级别下执行一致性读
.操作有了保证。换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值,这样在做查询的时候就不用等待另一个事务释放锁。
MVCC没有正式的标准,在不同的DBMS中MVCC的实现方式可能是不同的,也不是普遍使用的(大家可以参考相关的DBMS文档)。这里讲解InnoDB中 MVCC的实现机制(MySQL其它的存储引擎并不支持它)
MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突
,做到即使有读写冲突时,也能做到不加锁
,非阻塞并发读
,而这个读指的就是快照读
,而非当前读
。当前读实际上是一种加锁的操作,是悲观锁的实现。而MVCC本质是采用乐观锁思想的一种方式。
快照读又叫一致性读,读取的是快照数据。不加锁的简单的SELECT都属于快照读
MVCC n'a pas de norme formelle. L'implémentation de MVCC peut être différente selon les SGBD, et elle n'est pas universellement utilisée (vous pouvez vous référer à la documentation du SGBD correspondante). Voici une explication du mécanisme d'implémentation de MVCC dans InnoDB (les autres moteurs de stockage MySQL ne le prennent pas en charge)
conflits lecture-écriture
afin que même en cas de conflits de lecture-écriture, aucun verrouillage
et lecture simultanée non bloquante
puisse être obtenu , et Cette lecture fait référence à la lecture instantanée
, et non à la lecture actuelle
. La lecture actuelle est en fait une opération de verrouillage et une implémentation d'un verrouillage pessimiste. L'essence de MVCC est une manière d'utiliser une pensée de verrouillage optimiste. 2.1 Lecture d'instantanéLa lecture d'instantané est également appelée lecture cohérente et lit les données d'instantané. Les SELECT simples sans verrous sont des lectures d'instantanés
, ce qui signifie des lectures non bloquantes sans verrous, par exemple : select * from player where ...
La raison pour laquelle les lectures d'instantanés se produisent est d'améliorer les performances de concurrence. Considérez que l'implémentation de la lecture d'instantanés est nécessaire. basé sur MVCC, qui dans de nombreux cas évite les opérations de verrouillage et réduit les frais généraux. Comme il est basé sur plusieurs versions, l'instantané lu ne lit pas nécessairement la dernière version des données, mais peut être la version historique précédente.
Le principe de la lecture d'instantané est que le niveau d'isolement n'est pas le niveau série. La lecture d'instantané au niveau série dégénérera en lecture actuelle.
2.2 Lecture actuelleLa lecture actuelle lit la dernière version de l'enregistrement (les dernières données, pas la version historique des données). Lors de la lecture, il est également nécessaire de s'assurer que d'autres transactions simultanées ne peuvent pas modifier l'enregistrement actuel, et). l'enregistrement lu sera traité. Un SELECT verrouillé, ou un ajout, une suppression ou une modification de données entraînera la lecture actuelle.
3. Révision
3.1 Parlons à nouveau des niveaux d'isolementNous savons que les transactions ont 4 niveaux d'isolement, et il peut y avoir trois problèmes de concurrence :
Dans MysQL , le niveau d'isolement par défaut est une lecture répétable, ce qui peut résoudre le problème de la lecture sale et de la lecture non répétable, ne serait-ce que du point de vue de la définition, cela ne peut pas résoudre le problème de la lecture fantôme. Si nous voulons résoudre le problème de lecture fantôme, nous devons utiliser la sérialisation, c'est-à-dire augmenter le niveau d'isolement au niveau le plus élevé, mais cela réduira considérablement la capacité de concurrence des transactions de la base de données. MVCC peut résoudre les problèmes de lecture non répétables et de lecture fantôme grâce au verrouillage optimiste au lieu d'utiliser un mécanisme de verrouillage. Il peut remplacer les verrous au niveau des lignes dans la plupart des cas et réduire la surcharge du système ! 3.2 Champs cachés, chaîne de versions d'annulation du journalEn regardant la chaîne de versions d'annulation du journal, pour les tables utilisant le moteur de stockage InnoDB, ses enregistrements d'index cluster contiennent deux colonnes cachées nécessaires.
trx_id : chaque fois qu'une transaction modifie un enregistrement d'index clusterisé, l'identifiant de la transaction sera attribué à la colonne cachée trx_id. roll_pointer : Chaque fois qu'un enregistrement d'index clusterisé est modifié, l'ancienne version sera écrite dans le journal d'annulation. Cette colonne cachée équivaut à un pointeur à travers lequel les informations avant l'enregistrement peuvent être trouvées. 4.1 Qu'est-ce que ReadView.
en MV Mécanisme CC Dans , plusieurs transactions mettant à jour le même enregistrement de ligne généreront plusieurs instantanés historiques, et ces instantanés historiques sont enregistrés dans le journal d'annulation. Si une transaction souhaite interroger cet enregistrement de ligne, quelle version de l'enregistrement de ligne doit être lue ? À ce stade, ReadView doit être utilisé, ce qui nous aide à résoudre le problème de visibilité des lignes.Lorsqu'une transaction utilise le mécanisme MVCC pour effectuer une opération de lecture d'instantané, une vue de lecture sera générée, qui est ReadView. Lorsqu'une transaction est démarrée, un instantané actuel du système de base de données sera généré. InnoDB construit un tableau pour chaque transaction afin d'enregistrer et de conserver l'ID des transactions actives actuelles du système (« actives » fait référence à celles qui ont été démarrées mais n'ont pas encore été soumis. ).
Utilisez la transaction de niveau d'isolement READ UNCOMMITTED
, car vous pouvez lire les enregistrements modifiés par non validés. transactions, afin que vous puissiez directement lire la dernière version de l'enregistrement. READ UNCOMMITTED
隔离级别的事务,由于可以读到未提交事务修改过的记录,所以直接读取记录的最新版本就好了。
使用SERIALIZABLE
隔离级别的事务,InnoDB规定使用加锁的方式来访问记录。
使用READ COMMITTED
和REPEATABLE READ
隔离级别的事务,都必须保证读到已经提交了的事务
修改过的记录。假如另一个事务已经修改了记录但是尚未提交,是不能直接读取最新版本的记录的,核心问题就是需要判断一下版本链中的哪个版本是当前事务可见的,这是ReadView要解决的主要问题。
这个ReadView中主要包含4个比较重要的内容,分别如下:
有了这个ReadView,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见。
如果被访问版本的trx_id属性值与ReadView中的creator_trx_id
值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
如果被访问版本的trx_id属性值小于ReadView中的up_limit_id
值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
如果被访问版本的trx_id属性值大于或等于ReadView中的low_limit_id
值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
如果被访问版本的trx_id属性值在ReadView的up_limit_id
和low_limit_id
之间,那就需要判断一下trx_id属性值是不是在trx_ids
列表中。如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问。如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。 4.4 MVCC整体操作流程
了解了这些概念之后,我们来看下当查询一条记录的时候,系统如何通过MVCC找到它:
1.首先获取事务自己的版本号,也就是事务ID;
2.获取ReadView;
3.查询得到的数据,然后与ReadView中的事务版本号进行比较;
4.如果不符合Readview规则,就需要从Undo Log中获取历史快照;
5.最后返回符合规则的数据。
如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本。如果最近版本不可见,则该记录对该事务不可见,并且查询结果中不包含该记录。
InnoDB中,MVCC是通过Undo Log + Read View进行数据读取,Undo Log保存了历史快照,而Read View规则帮我们判断当前版本的数据是否可见。
在隔离级别为读已提交(Read Committed)时,一个事务中的每一次select查询都会重新获取一次Read View。
当隔离级别为可重读的时候,就避免了不可重复读,这是因为一个事务只在第一次select的时候会获取一次Read View,而后面所有的select都会复用这个Read View,
如下所示:
假设现在student表中只有一条由事务id
为8
的事务插入一条记录:
MVCC只能在READ COMMITTED和REPEATABLE READ两个隔离级别下工作。接下来看一下READ COMMITTED
和REPEATABLE READ
所谓的生成Readview的时机不同到底不同在哪里。
隔离级别下:
READ COMMITTED:每次读取数据前都生成一个ReadView
SERIALIZABLE
InnoDB nécessite un verrouillage pour accéder aux enregistrements. #Les transactions utilisant les niveaux d'isolement READ COMMITTED
et REPEATABLE READ
doivent garantir que les enregistrements modifiés par la transaction validée
sont lus si une transaction a modifié le. mais ne l'a pas encore soumis, il ne peut pas lire directement la dernière version de l'enregistrement. Le problème principal est de déterminer quelle version de la chaîne de versions est visible pour la transaction en cours. C'est le principal problème à résoudre par ReadView #🎜. 🎜##🎜🎜#Ce ReadView contient principalement 4 contenus importants, qui sont les suivants :#🎜🎜##🎜🎜##🎜🎜##🎜🎜#4.3 Règles ReadView#🎜🎜##🎜🎜#Oui Ceci ReadView est utilisé, de sorte que lors de l'accès à un enregistrement, il vous suffit de suivre les étapes ci-dessous pour déterminer si une certaine version de l'enregistrement est visible #🎜🎜#creator_trx_id
dans ReadView, cela signifie que la transaction en cours accède à son propre enregistrement modifié, cette version peut donc être accessible par la transaction en cours #🎜🎜#<.>up_limit_id
dans ReadView, cela indique que la transaction qui a généré cette version a été soumise avant que la transaction en cours n'ait généré ReadView, cette version est donc accessible par la transaction en cours #🎜🎜#low_limit_id
dans ReadView, cela indique que la version est générée. La transaction est ouverte après que la transaction en cours ait généré ReadView, cette version n'est donc pas accessible par la transaction en cours. . #🎜🎜#up_limit_id
et low_limit_id
de ReadView, alors le jugement est obligatoire Vérifiez si la valeur de l'attribut trx_id est dans la liste trx_ids
. Si tel est le cas, cela signifie que la transaction qui a généré cette version était toujours active lors de la création de ReadView et que cette version n'est pas accessible. Sinon, cela signifie que la transaction qui a généré cette version lors de la création de ReadView a été validée et que cette version est accessible. 4.4 Processus de fonctionnement global de MVCC#🎜🎜#identifiant de transaction
de 8
:#🎜🎜##🎜🎜##🎜🎜##🎜🎜#MVCC ne peut être utilisé qu'en READ COMMITTED et REPEATABLE READ fonctionnent sous deux niveaux d'isolement. Examinons ensuite la différence dans le timing de génération de Readview entre READ COMMITTED
et REPEATABLE READ
. #🎜🎜##🎜🎜#5.1 READ COMMITTED#🎜🎜##🎜🎜#Sous niveau d'isolement :#🎜🎜##🎜🎜#READ COMMITTED : avant chaque lecture de données Tous générer un ReadView
#🎜🎜#Il y a maintenant deux transactions avec les ID de transaction 10 et 20 en cours d'exécution :
Remarque : Pendant l'exécution de la transaction, uniquement lorsque l'enregistrement est réellement modifié pour la première fois (par exemple en utilisant INSERT, DELETE, UPDATE déclaration), un ID de transaction distinct sera attribué et cet ID de transaction sera incrémenté. C'est pourquoi nous avons mis à jour certains enregistrements dans d'autres tables de la transaction 2 afin de lui permettre d'attribuer des identifiants de transaction.
À l'heure actuelle, la liste chaînée des versions obtenue par l'enregistrement avec l'identifiant 1 dans la table étudiant est la suivante :
Supposons qu'une transaction utilisant le niveau d'isolement READ COMMITED commence à s'exécuter :
Ceci Le processus d'exécution de SELECT1 est le suivant :
Étape 1∶Lors de l'exécution de l'instruction SELECT, un ReadView
sera généré en premier. Le contenu du trx_ids de ReadView. La liste
est [10, 20], up_limit_id
est 10
, low_limit_id
est 21
, creator_trx_id
est 0
. ReadView
,ReadView的trx_ids
列表的内容就是[10,20],up_limit_id
为10
, low_limit_id
为21
, creator_trx_id
为0
。
步骤2:从版本链中挑选可见的记录,从图中看出,最新版本的列name
的内容是'王五'
,该版本的trx_id
值为10
,在trx_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。
步骤3:下一个版本的列name
的内容是'李四'
,该版本的trx_id
值也为10
,也在trx_ids
列表内,所以也不符合要求,继续跳到下一个版本。
步骤4:下一个版本的列 name
的内容是‘张三'
,该版本的trx_id
值为8
,小于ReadView 中的up_limit_id值10,所以这个版本是符合要求的,最后返回给用户的版本就是这条列name为‘张三’的记录。
之后,我们把事务id
为10
的事务提交一下:
然后再到事务id
为20
的事务中更新一下表student
中id
为1
Étape 2 : Sélectionnez les enregistrements visibles dans la chaîne de versions. Comme le montre la figure, le contenu de la colonne name
de la dernière version est '王五'
, et la version de trx_id est 10
, qui se trouve dans la liste trx_ids, elle ne répond donc pas aux exigences de visibilité et passe à la version suivante en fonction du roll_pointer.
name
de la prochaine version est '李思'
, et la valeur de trx_id
de cette version est également 10
est également dans la liste trx_ids
, il ne répond donc pas aux exigences et continue de passer à la version suivante. Étape 4 : Le contenu de la colonne name
de la version suivante est ‘Zhang San'
, et la valeur de trx_id
de cette version est 8
, qui est inférieur à la valeur up_limit_id 10 dans ReadView, cette version répond donc aux exigences. La version renvoyée à l'utilisateur est l'enregistrement avec le nom de colonne « Zhang San ».
id
de la transaction comme 10
: Ensuite, accédez à la transaction dont l'
identifiant de transaction
est 20 code> Mettre à jour l'enregistrement avec <code>id
comme 1
dans la table student
:
À ce moment, la version de l'enregistrement avec l'identifiant 1 dans la table étudiant La chaîne ressemble à ceci :
Continuez ensuite la recherche de l'enregistrement avec l'identifiant 1 dans la transaction qui vient d'utiliser le niveau d'isolement READ COMMITTED, comme suit :
Le processus d'exécution de ce SELECT2 est le suivant :
Étape 1∶
Lors de l'exécution de l'instruction SELECT, un ReadView sera généré séparément. Le contenu de la liste trx_ids du ReadView est [20], up_limit_id est 20, low_limit_id est 21 et Creator_trx_id est 0.
Étape 3∶Le contenu du nom de colonne de la prochaine version est "Qian Qi". La valeur trx_id de cette version est 20, qui est également dans la liste trx_ids, elle ne répond donc pas aux exigences et continue de sauter. à la prochaine version.
Étape 4 : Le contenu du nom de colonne de la version suivante est "王五". La valeur trx_id de cette version est 10, ce qui est inférieur à la valeur up_limit.id 20 dans ReadView, cette version répond donc aux exigences et est finalement renvoyé à l'utilisateur La version est l'enregistrement dont le nom de colonne est "王五".Par analogie, si l'enregistrement avec l'ID de transaction 20 est également soumis plus tard, lorsque l'enregistrement avec la valeur d'ID 1 dans la table student est à nouveau interrogé dans une transaction utilisant le niveau d'isolement READ CONMMITTED, le résultat sera "Song Ba", nous ne le ferons pas analyser le processus spécifique.
5.2 REPEATABLE READ
Sous le niveau d'isolement : Pour les transactions utilisant le niveau d'isolement REPEATABLE READ, un ReadView ne sera généré que lorsque l'instruction de requête est exécutée pour la première fois, et les requêtes suivantes ne seront pas générées à plusieurs reprises. . 🎜🎜🎜Par exemple, deux transactions avec les ID de transaction 10 et 20 sont en cours d'exécution dans le système : 🎜🎜🎜🎜🎜此刻,表student中id为1的记录得到的版本链表如下所示:
假设现在有一个使用REPEATABLE READ隔离级别的事务开始执行:
此时执行过程与read committed相同
然后再到刚才使用REPEATABLE READ隔离级别的事务中继续查找id为1的记录,如下:
这个SELECT2的执行过程如下:
步骤1:因为当前事务的隔离级别为REPEATABLE READ,而之前在执行SELECT1时已经生成过ReadView了,所以此时直接复用之前的ReadView,之前的ReadView的trx_ids列表的内容就是[10,20],up_limit_id为10, low_limit_id为21 , creator_trx_id为0。
步骤2:然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列name的内容是’宋八’trx_id值为20,在trx_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。
步骤3:下一个版本的列name的内容是’钱七’,该版本的trx_id值为20,也在trx_ids列表内合要求,继续跳到下一个版本。
步骤4:下一个版本的列name的内容是’王五’,该版本的trx_id值为10,而trx_ids列表中是包含值为10的事务id的,所以该版本也不符合要求,同理下一个列name的内容是’李四’的版本也不符合要求。继续跳到下个版本。
步聚5∶下一个版本的列name的内容是’张三’,该版本的trx_id值为80,小于Readview中的up_limit_id值10,所以这个版本是符合要求的,最后返回给用户的版本就是这条列c为‘张三’的记录。
两次SELECT查询得到的结果是重复的,记录的列c值都是’张三’,这就是可重复读的含义。如果我们之后再把事务id为20的记录提交了,然后再到刚才使用REPEATABLE READ隔离级刷的事务中继续查找这个id为1的记录,得到的结果还是’张三’,具体执行过程大家可以自己分析一下。
假设现在表student中只有一条数据,数据内容中,主键id=1,隐藏的trx_id=10,它的undo log如下图所示。
假设现在有事务A和事务B并发执行,事务A的事务id为20,事务B的事务id为30。
步骤1:事务A开始第一次查询数据,查询的SQL语句如下。
select * from student where id > 1;
在开始查询之前,MySQL会为事务A产生一个ReadView,此时ReadView的内容如下: trx_ids=[20, 30 ] ,up_limit_id=20 , low_limit_id=31 , creator_trx_id=20。
因为表student只有一条符合条件 where id>=1 的数据,所以会被查询出来。然后根据ReadView机制,发现该行数据的trx_id=10,小于事务A的ReadView里up_limit_id,这表示这条数据是事务A开启之前,其他事务就已经提交了的数据,因此事务A可以读取到。
结论:事务A的第一次查询,能读取到一条数据,id=1。
步骤2∶接着事务B(trx_id=30),往表student中新插入两条数据,并提交事务。
insert into student(id,name) values(2,'李四'); insert into student(id,name) values(3,'王五');
此时表student中就有三条数据了,对应的undo如下图所示:
Étape 3∶Ensuite, la transaction A démarre la deuxième requête. Selon les règles du niveau d'isolement de lecture répétable, la transaction A ne régénérera pas ReadView pour le moment. À ce stade, les trois éléments de données de la table des étudiants remplissent tous la condition où id>=1, ils seront donc trouvés en premier. Ensuite, selon le mécanisme ReadView, il est jugé si chaque élément de données peut être vu par la transaction A.
1) Tout d'abord, les données avec id=1, comme mentionné précédemment, peuvent être vues par la transaction A.
2) Ensuite, il y a les données avec id=2, et son trx_id=30. A ce moment, la transaction A constate que cette valeur est comprise entre up_limit_id et low_limit_id, il est donc nécessaire de déterminer si 30 est dans le tableau trx_ids. Puisque trx_ids=[20,30] de la transaction A, dans le tableau, cela signifie que les données avec id=2 ont été soumises par d'autres transactions démarrées en même temps que la transaction A, donc ces données ne peuvent pas être vues par la transaction A. .
3) De même, le trx_id de ces données avec id=3 est également 30, donc il ne peut pas être vu par la transaction A.
Conclusion :Enfin, la deuxième requête de la transaction A ne peut interroger que les données avec id=1. C'est le même résultat que la première requête de la transaction A, il n'y a donc pas de phénomène de lecture fantôme. Par conséquent, sous le niveau d'isolement répétable de MySQL, il n'y a pas de problème de lecture fantôme.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!