Maison  >  Article  >  base de données  >  Un article analysant brièvement comment MySQL résout le problème de lecture fantôme

Un article analysant brièvement comment MySQL résout le problème de lecture fantôme

青灯夜游
青灯夜游avant
2023-02-06 20:14:162004parcourir

Comment MySQL résout-il le problème de lecture fantôme ? L’article suivant vous permettra d’aborder cette problématique. Lisons l’article avec des questions !

Un article analysant brièvement comment MySQL résout le problème de lecture fantôme

  Parmi les questions d'entretien très fréquentes de Jin Bu San et Yin Bu Si, les caractéristiques des transactions de MySQL, le niveau d'isolement et d'autres problèmes sont également l'un des essais en huit parties les plus classiques. On estime que la plupart des amis pourront le récupérer facilement. Choses :

Caractéristiques de la transaction (ACID) : Atomicité (Atomicité), . Isolement (Isolement >), Cohérence et Persistance原子性Atomicity)、隔离性Isolation)、一致性Consistency)和持久性

隔离级别读取未提交READ UNCOMMITTED),读取已提交READ COMMITTED),可重复读REPEATABLE READ),可串行化SERIALIZABLE

而每一种隔离级别导致的问题有:

  • READ UNCOMMITTED隔离级别下,可能发生脏读不可重复读幻读问题
  • READ COMMITTED隔离级别下,可能发生不可重复读幻读问题,但是不可以发生脏读问题
  • REPEATABLE READ隔离级别下,可能发生幻读问题,但是不可以发生脏读不可重复读的问题
  • SERIALIZABLE隔离级别下,各种问题都不可以发生

对于MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读),从上面的SQL标准的四种隔离级别定义可知,REPEATABLE-READ(可重复读)是不可以防止幻读的,但是我们都知道,MySQL InnoDB存储引擎是解决了幻读问题发生的,那他又是如何解决的呢?

1. 行格式

  在进入主题之前,我们先大致了解一下什么是行格式,这样有助于我们理解下面的MVCC,行格式是表中的行记录在磁盘的存放方式,Innodb存储引擎总共有4种不同类型的行格式:compactredundantdynamiccompress;虽然很很多行格式,但是在原理上,大体都相同,如下,为compact行格式:Un article analysant brièvement comment MySQL résout le problème de lecture fantôme  从图中可以看出来,一条完整的记录其实可以被分为记录的额外信息记录的真实数据两大部分,记录的额外信息分别是变长字段长度列表NULL值列表记录头信息,而记录的真实数据除了我们自己定义的列之外,MySQL会为每个记录添加一些默认列,这些默认列又称为隐藏列

Niveau d'isolement : Lecture non validé (LECTURE UNCOMMITTED), Lecture validée (LECTURE COMMITTED), Lecture répétable (LECTURE RÉPÉTABLE code>), Sérialisable (SERIALIZABLE)
  • READ UNCOMMITTEDSous le niveau d'isolement, des problèmes de lecture sale, de lecture non répétable et de lecture fantôme peuvent survenir
  • Sous le Niveau d'isolement READ COMMITTED, des lectures non répétables et des lectures fantômes peuvent se produire, mais des lectures sales ne peuvent pas se produire >Problèmeli>
  • Sous le niveau d'isolement REPEATABLE READ, le problème de lecture fantôme peut survenir, mais la lecture sale et Non répétable lireproblèmes
  • SÉRIALISABLESous le niveau d'isolement, divers problèmes ne peuvent pas survenir
REPEATABLE-READ (répétable)Lecture fantôme. , mais nous savons tous que le moteur de stockage MySQL InnoDB résout le problème de la lecture fantôme, alors comment le résout-il ?

1. Format de ligne Nom de la colonne. LongueurDescriptionrow_id6 octetsID de ligne, identifie de manière unique un enregistrementtransaction_id
Les problèmes causés par chaque niveau d'isolement sont : Pour le stockage MySQL InnoDB Le niveau d'isolement par défaut pris en charge par le Le moteur est D'après la définition des quatre niveaux d'isolement du standard SQL ci-dessus, on peut voir que REPEATABLE-READ (lecture répétable) ne peut pas être empêché
  Avant d'entrer dans le sujet, comprenons de manière générale ce qu'est le format de ligne, ce qui nous aidera à comprendre le MVCC suivant, row Le format est la façon dont les enregistrements de ligne dans la table sont stockés sur le disque. Le moteur de stockage Innodb a un total de 4 types différents de formats de ligne : compact, . redondant , dynamic, compress ; bien qu'il existe de nombreux formats de ligne, ils sont fondamentalement les mêmes. Ce qui suit est le compact format de ligne : <img src="https://img.php.cn/upload/article/000/000/024/8dd59fa5971263caaad55b052cdb5754-0.png" alt="Un article analysant brièvement comment MySQL résout le problème de lecture fantôme" chargement="lazy">  Comme Comme le montre l'image, un enregistrement complet peut en fait être divisé en deux parties : les <code>informations supplémentaires enregistrées et les données réelles enregistrées. > sont respectivement Liste de longueur de champ variable, Liste de valeurs NULL et Informations d'en-tête d'enregistrement, et Données réelles enregistrées sauf notre propre définition En plus des colonnes, MySQL ajoutera des colonnes par défaut à chaque enregistrement. Ces colonnes par défaut sont également appelées colonnes cachées. Les colonnes spécifiques sont les suivantes :
🎜6 octets🎜🎜ID de transaction🎜🎜🎜 🎜roll_pointer🎜🎜7 octets 🎜🎜 réponse Rolling pointeur🎜🎜🎜🎜

Nous n'avons pas à nous soucier de la valeur de la colonne cachée. Le moteur de stockage InnoDB la générera pour nous. Dessinons-la plus en détail. le format est le suivant : InnoDB存储引擎会自己帮我们生成的,画得再详细一点,compact行格式如下:

Un article analysant brièvement comment MySQL résout le problème de lecture fantôme

  • transaction_id :事物id,当事物对行记录进行修改时,都会将本事物的事物id赋值到该列
  • roll_pointer:每次在对行记录进行改动的时候,都会把旧版本的数据写入undolog日志,然后将roll_pointer 指向该undolog,所以该列相当于一个指针,通过该列,可以找到修改之前的信息

2. MVCC详解

2.1 版本链

假设有一条记录如下:Un article analysant brièvement comment MySQL résout le problème de lecture fantôme插入该记录的事务id80roll_pointer 指针为NULL(为了便于理解,读者可理解为指向为NULL,实际上roll_pointer第一个比特位就标记着它指向的undo日志的类型,如果该比特位的值为1时,就代表着它指向的undo日志类型为insert undo)

假设之后两个事务id分别为100200的事务对这条记录进行UPDATE操作:

 -- 事务id=100
 update person set grade =20 where id =1;
 update person set grade =40 where id =1;
 -- 事务id=200
 update person set grade =70 where id =1;

  每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来,串成一个链表,所以现在的情况就像下图一样:

Un article analysant brièvement comment MySQL résout le problème de lecture fantôme  对该记录每次更新后,都会将旧值放到一条undo日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被roll_pointer属性连接成一个链表,我们把这个链表称之为版本链,版本链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务id

2.2 ReadView

  对于数据库的四种隔离级别:1)read uncommitted;2) read committed;3) REPEATABLE READ; 4)SERIALIZABLE;来说,READ UNCOMMITTED,每次读取版本链的最新数据即可;SERIALIZABLE,主要是通过加锁控制;而read committedREPEATABLE READ都是读取已经提交了的事物,所以对于这两个隔离级别,核心问题是版本链中,哪些事物是对当前事物可见;为了解决这个问题,MySQL提出了read view 概念,其包含四个核心概念:

  • m_ids:生成read view 时候,活跃的事物id集合
  • min_trx_idm_ids的最小值,既生成read view的时候,活跃事物的最小值
  • max_trx_id:表示生成read view的时候,系统应该分配下一个事物id值
  • creator_trx_id:创建read view的事物id,即当前事物id。

有了这个ReadView,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:Un article analysant brièvement comment MySQL résout le problème de lecture fantôme

  • 当记录的事物id等于creator_trx_id的时候,说明当前事物正在访问自己修改的记录,所以该版本可见
  • 如果被访问的版本事物id小于min_trx_id的时候,则说明,在创建read view的时候,该事物已经提交,该版本,对当前事物可读
  • 如果被访问的版本事物id大于或等于max_trx_id,则说明创建该read view的时候,该说明生成该版本记录的事物id在生成Read view之后才开启,所以该版本不能被当前事物可读
  • 如果被访问的版本事物transaction_idm_ids集合中,说明生成Read view的时候,该事物还是活跃的,还没有被提交,则该版本不可以被访问;如果不在,则说明创建ReadView
  • image .png
  • transaction_id : identifiant de transaction. Lorsqu'une transaction modifie un enregistrement de ligne, l'identifiant de cette transaction sera attribué dans cette colonne

  • roll_pointer : à chaque fois que vous apportez des modifications aux enregistrements de ligne, l'ancienne version des données sera écrite dans le journal undolog, puis roll_pointer pointe vers le undolog, donc cette colonne équivaut à un pointeur, passé Dans cette colonne, vous pouvez retrouver les informations avant modification🎜<h2 data-id="heading-1"> <strong>Détails MVCC</strong>. </h2> <h3 data-id="heading-2"> <strong>Chaîne de versions 2.1</strong> </h3>🎜Supposons qu'il existe un enregistrement comme suit : <img src="https://img.%20php.cn/upload/article/000/000/024/a3789b0f8f8d61a35c454c5a61c58645-2.%20png" alt="Un article analysant brièvement comment MySQL résout le problème de lecture fantôme" chargement="lazy">Le <code>identifiant de transaction inséré dans cet enregistrement est 80, et le pointeur roll_pointer est NULL (Pour faciliter la compréhension, les lecteurs peuvent comprendre que le pointeur est NULL. En fait, le premier bit de roll_pointer marque le type de journal d'annulation vers lequel il pointe. Si la valeur de ce bit est 1, il représente le journal d'annulation vers lequel il pointe. Le type est insert undo)🎜🎜Supposons que les deux transactions suivantes avec ID de transaction soient . 100 et 200 pour METTRE À JOUR cet enregistrement Opération : 🎜rrreee🎜  Chaque fois qu'un enregistrement est modifié, un undo log sera enregistré , et chaque undo log possède également un attribut roll_pointer code> (le undo log correspondant à l'opération INSERT ne n'ont pas cet attribut, car l'enregistrement n'a pas de version antérieure), ces journaux d'annulationTous sont connectés et enchaînés dans une liste chaînée, donc la situation actuelle est comme l'image ci-dessous : 🎜🎜Un article analysant brièvement comment MySQL résout le problème de lecture fantôme Par conséquent, à chaque fois que l'enregistrement est mis à jour , l'ancienne valeur sera placée dans un undo log, même s'il s'agit d'une ancienne valeur de l'enregistrement Version, à mesure que le nombre de mises à jour augmente, toutes les versions seront connectées dans une liste chaînée par le roll_pointer. Nous appelons cette liste chaînée chaîne de versions, le nœud principal de la chaîne de versions. C'est la dernière valeur de l'enregistrement actuel. De plus, chaque version contient également l'ID de transaction🎜

    2.2 ReadView

    🎜 Pour les quatre niveaux d'isolement correspondants de la base de données : 1)lecture non validée; 2) lecture validée 3) LECTURE REPEATABLE 4)SERIALIZABLE ; ; Par exemple, READ UNCOMMITTED n'a besoin de lire que les dernières données de la chaîne de versions à chaque fois ; SERIALIZABLE est principalement contrôlé par le verrouillage et read commit ; > et REPEATABLE READ lisent tous deux les éléments validés, donc pour ces deux niveaux d'isolement, le problème principal est de savoir quels éléments de la chaîne de versions sont visibles par l'élément actuel afin de résoudre ce problème, MySQL a proposé le ; concept de vue en lecture, qui contient quatre concepts de base : 🎜
    • m_ids : lors de la génération de la vue en lecture, l'ID de l'objet actif est défini 🎜
    • min_trx_id : La valeur minimale de m_ids, qui est la valeur minimale des éléments actifs lors de la génération de la vue en lecture🎜
    • max_trx_id : Indique que lors de la génération de lire la vue, le système doit attribuer la valeur d'identifiant d'objet suivante 🎜
    • creator_trx_id : créer l'identifiant d'objet de lire la vue, c'est-à-dire le identifiant de l'objet actuel. 🎜🎜🎜Avec ce ReadView, 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 : Un article analysant brièvement comment MySQL résout le problème de lecture fantôme🎜
      • Lorsque l'identifiant de l'objet enregistré est égal à creator_trx_id , cela signifie que l'objet actuel accède à son propre enregistrement modifié, donc cette version est visible🎜
      • Si l'identifiant de l'objet de la version consultée est inférieur à min_trx_id, cela signifie que lors de la création de read view, l'objet a été soumis et cette version est lisible pour l'objet actuel🎜
      • Si l'ID de l'objet de la version consultée est supérieur ou égal à max_trx_id, cela signifie qu'il est créé. Lorsque <code>read view est généré, cela signifie que l'ID d'objet qui génère cet enregistrement de version n'est pas ouvert tant que Read view n'est pas généré, donc cette version ne peut pas être lue par l'objet actuel🎜
      • Si la transaction de version accédée transaction_id est dans la collection m_ids, cela signifie que lorsque Lire la vue code> est généré, la transaction est toujours active et n'a pas encore été soumise, alors 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 <code>ReadView a été soumise ; et accessible🎜🎜🎜Remarque : l'ID de l'objet lu est 0🎜

        Dans MySQL, une très grande différence entre les niveaux d'isolement de READ COMMITTED et REPEATABLE READ est qu'ils génèrent ReadView à des moments différents : MySQL中,READ COMMITTEDREPEATABLE READ隔离级别的的一个非常大的区别就是它们生成ReadView的时机不同:

        • READ COMMITTED —— 每次读取数据前都生成一个ReadView
        • REPEATABLE READ —— 在第一次读取数据时生成一个ReadView

        下面我们通过详细例子来说明,两者有何不同:

        时间编号
        trx 100 trx 200
        BEGIN;


        BEGIN; BEGIN;

        update person set grade =20 where id =1;

        update person set grade =40 where id =1;
        SELECT * FROM person WHERE id = 1;


        COMMIT;


        update person set grade =70 where id =1;
        SELECT * FROM person WHERE id = 1;



        COMMIT;
        ? COMMIT;

        在时间④中,因事务trx 100 执行了事务的提交,id=1行记录的版本链如下:

        Un article analysant brièvement comment MySQL résout le problème de lecture fantôme在时间⑥中,因事务trx 200 执行了事务的提交,id=1行记录的版本链如下:

        Un article analysant brièvement comment MySQL résout le problème de lecture fantôme

        在时间⑤,事务trx 100执行select语句时会先生成一个ReadViewReadViewm_ids列表的内容就是[100, 200]min_trx_id100max_trx_id201creator_trx_id0,此时,从版本链中选可见的记录,版本链从上到下遍历:因为grade=40,trx_id值为100,在m_ids里,所以该记录不可见,同理,grade=20的也不见。继续往下遍历,grade=20,trx_id值为80,小于小于ReadView中的min_trx_id100,所以这个版本符合要求,返回给用户的是等级为10的记录。

        在时间⑧中,如果事务的隔离级别是READ COMMITTED,会单独又生成一个ReadView,该ReadViewm_ids列表的内容就是[200]min_trx_id200max_trx_id201creator_trx_id0,此时,从版本链中选可见的记录,版本链从上到下遍历:因为grade=70,trx_id值为200,在m_ids里,所以该记录不可见,继续往下遍历,grade=40,trx_id值为100,小于ReadView中的min_trx_id200,所以这个版本是符合要求的,返回给用户的是是等级为40的记录。

        在时间⑧中,如果事务的隔离级别是 REPEATABLE READ,在时间⑧中,不会单独生成一个ReadView,而是沿用时间5的ReadView,所以返回给用户的等级是10。前后两次select得到的是一样的,这就是可重复读的含义。

        3. 总结

          通过分析MVCC详解部分,可以得出,基于MVCC,在RR隔离级别下,很好解决了幻读问题,但是我们知道,select for update是产生当前读,不再是快照读,那么此种情况,MySQL又是怎么解决幻读问题的呢?基于时间问题(整理画图的确需要花比较多的时间),此处先给结论,后面再分析在当前读的情况下,MySQL是怎么解决幻读

          READ COMMITTED —— Générez un ReadView à chaque fois avant de lire les données
        • REPEATABLE READ —— Lors de la première lecture des données, générez un ReadView
      Utilisons des exemples détaillés pour illustrer la différence entre les deux :

      Numéro d'heure trx 100 trx 200
      ① td> COMMENCER ;


      BEGIN; BEGIN;

      >
      mettre à jour la note définie par la personne =20 où l'identifiant =1 ;

      >
      mettre à jour le grade de la personne =40 où id =1 ;
      ⑤ td> SELECT * FROM personne WHERE id = 1;


      COMMIT ;

      mettre à jour la personne définie la note =70 où id =1 ;
      SELECT * FROM personne WHERE id = 1 ;



      COMMIT;
      ? COMMIT ;

      à l'heure ④, due à la transaction trx 100 exécutant la soumission de la transaction, la chaîne de versions enregistrée dans la ligne id=1 est la suivante :

      Un article analysant brièvement comment MySQL résout le problème de lecture fantômeAu moment ⑥, en raison de la transaction trx 200 exécutant la soumission de la transaction, la version La chaîne de l'enregistrement de ligne id=1 est la suivante :

      image. png🎜🎜Au moment ⑤, lorsque la transaction trx 100 exécute l'instruction select, elle générera d'abord un ReadView, Le contenu de la liste <code>m_ids de ReadView est [100, 200], min_trx_id est 100, max_trx_id est 201 et creator_trx_id est 0. À ce stade, sélectionnez le visible. enregistrement de la chaîne de versions. La chaîne de versions commence du haut Traverse au suivant : Parce que grade=40, la valeur de trx_id est 100, qui est dans . m_ids, donc l'enregistrement est invisible. De même, grade= Ceux de moins de 20 ans sont également manquants. Continuez à descendre, grade = 20, la valeur trx_id est 80, ce qui est inférieur à la valeur min_trx_idReadView code>100, cette version répond donc aux exigences et les enregistrements de niveau 10 sont renvoyés à l'utilisateur. 🎜🎜Au moment ⑧, si le niveau d'isolement de la transaction est READ COMMITTED, un ReadView distinct sera généré, et le de ce <code>ReadView Le contenu de la liste m_ids est [200], min_trx_id est 200 et max_trx_id code> est <code> 201, creator_trx_id est 0 À ce stade, sélectionnez l'enregistrement visible dans la chaîne de versions et parcourez la chaîne de versions depuis le haut. vers le bas : parce que grade=70, la valeur de trx_id est 200, qui est en m_ids, donc l'enregistrement n'est pas visible. Continuez à parcourir vers le bas, grade=40,trx_id >La valeur est 100, ce qui est inférieur à la valeur min_trx_id 200 dans ReadView, pour que cette version réponde aux exigences, renvoie Ce qui est donné à l'utilisateur est un enregistrement de niveau 40. 🎜🎜Au temps ⑧, si le niveau d'isolement de la transaction est REPEATABLE READ, au temps ⑧, un ReadView séparé ne sera pas généré, mais le ReadView, donc le niveau renvoyé à l'utilisateur est 10. Le résultat des deux sélections est le même. C'est le sens de la lecture répétable. 🎜

      🎜3. Résumé🎜

      🎜  En analysant l'explication détaillée de MVCC, on peut conclure que sur la base de MVCC, sous le niveau d'isolement RR, le phantom est bien résolu le problème de lecture, mais nous savons que select for update génère la lecture actuelle et n'est plus une lecture instantanée. Dans ce cas, comment MySQL résout-il phantom read<.> >Qu'en est-il du problème ? En fonction du problème de temps (il faut beaucoup de temps pour trier le dessin), je vais d'abord donner la conclusion ici, puis analyser comment MySQL résout le problème de <code>lecture fantôme dans la situation de lecture actuelle. : 🎜🎜🎜🎜 Lecture actuelle🎜 : utilisez le verrouillage à clé suivante (verrouillage d'espacement) pour verrouiller afin de garantir que les lectures fantômes ne se produisent pas🎜🎜🎜Quant à la façon dont le verrouillage d'espacement résout le problème de lecture fantôme dans la situation de lecture actuelle, les amis intéressés peuvent ajoutez un suivi J'aime🎜🎜[Recommandations associées : 🎜Tutoriel vidéo MySQL🎜]🎜

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!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer