Heim  >  Artikel  >  Datenbank  >  Ein Artikel, der kurz analysiert, wie MySQL das Phantomleseproblem löst

Ein Artikel, der kurz analysiert, wie MySQL das Phantomleseproblem löst

青灯夜游
青灯夜游nach vorne
2023-02-06 20:14:162070Durchsuche

Wie löst MySQL das Phantom-Read-Problem? Im folgenden Artikel können Sie über dieses Problem sprechen. Lesen Sie den Artikel mit Fragen!

Ein Artikel, der kurz analysiert, wie MySQL das Phantomleseproblem löst

  Unter den hochfrequenten Interviewfragen von Jin Bu San und Yin Bu Si sind die Transaktionseigenschaften, der Isolationsgrad und andere Themen auch einer der sehr klassischen achtteiligen Aufsätze Es wird geschätzt, dass die meisten Freunde es leicht erlernen können. Dinge:

Transaktionsmerkmale (ACID): Atomicity (Atomicity), Isolation (Isolation >), Konsistenz und Persistenz原子性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行格式:Ein Artikel, der kurz analysiert, wie MySQL das Phantomleseproblem löst  从图中可以看出来,一条完整的记录其实可以被分为记录的额外信息记录的真实数据两大部分,记录的额外信息分别是变长字段长度列表NULL值列表记录头信息,而记录的真实数据除了我们自己定义的列之外,MySQL会为每个记录添加一些默认列,这些默认列又称为隐藏列

Isolationsstufe: Read Uncommitted (READ UNCOMMITTED), Read Committed (READ COMMITTED), Repeatable Read (REPEATABLE READ code>), <code>Serializable (SERIALIZABLE)
  • READ UNCOMMITTEDUnder Die Isolationsstufe, Dirty Read, Non-Repeatable Read und Phantom Read können Probleme auftreten
  • Unter dem READ COMMITTED-Isolationsstufe, nicht wiederholbare Lesevorgänge und Phantom-Lesevorgänge können auftreten, Dirty Reads können jedoch nicht auftreten. li>
  • Unter der Isolationsstufe REPEATABLE READ kann das Problem Phantom Read auftreten, aber Dirty Read und Non-repeatable readProbleme
  • SERIALIZABLEUnter der Isolationsstufe können verschiedene Probleme nicht auftreten
REPEATABLE-READ (wiederholbar)Phantom-Lesen , aber wir alle wissen, dass die MySQL InnoDB-Speicher-Engine das Problem des Phantomlesens löst. Wie löst sie es also?

1. Zeilenformat SpaltennameLänge BeschreibungZeilen-ID6 BytesZeilen-ID, identifiziert einen Datensatz eindeutigTransaktions-ID
Die durch jede Isolationsstufe verursachten Probleme sind: Für MySQL InnoDB-Speicher Die von der unterstützte Standardisolationsstufe Engine ist Aus der Definition der vier Isolationsstufen des SQL-Standards oben ist ersichtlich, dass REPEATABLE-READ (wiederholbares Lesen) nicht verhindert werden kann
  Bevor wir mit dem Thema beginnen, wollen wir uns ein allgemeines Verständnis über das Zeilenformat verschaffen, das uns hilft, den folgenden MVCC zu verstehen: row Das Format ist die Art und Weise, wie Zeilendatensätze in der Tabelle auf der Festplatte gespeichert werden. Die Innodb-Speicher-Engine verfügt über insgesamt 4 verschiedene Arten von Zeilenformaten: compact, redundant , dynamic, compress; Obwohl es viele Zeilenformate gibt, sind sie im Prinzip gleich. Das Folgende ist der compact Zeilenformat: Ein Artikel, der kurz analysiert, wie MySQL das Phantomleseproblem löst  As Wie aus dem Bild hervorgeht, kann ein vollständiger Datensatz tatsächlich in zwei Teile unterteilt werden: Aufgezeichnete Zusatzinformationen und Aufgezeichnete Zusatzinformationen > ist Liste variabler Feldlänge, NULL-Werteliste und Header-Informationen aufzeichnen sowie Aufgezeichnete reale Daten außer unseren eigenen Definition Zusätzlich zu den Spalten fügt MySQL jedem Datensatz einige Standardspalten hinzu. Diese Standardspalten werden auch als versteckte Spalten bezeichnet. Die spezifischen Spalten lauten wie folgt:
🎜6 Bytes🎜🎜Transaktions-ID🎜🎜🎜 🎜roll_pointer🎜🎜7 Byte 🎜🎜 Antwort Rollzeiger 🎜🎜🎜🎜

Über den Wert der versteckten Spalte müssen wir uns keine Gedanken machen. Die InnoDB-Speicher-Engine wird sie für uns generieren Das Format ist wie folgt: InnoDB存储引擎会自己帮我们生成的,画得再详细一点,compact行格式如下:

Ein Artikel, der kurz analysiert, wie MySQL das Phantomleseproblem löst

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

2. MVCC详解

2.1 版本链

假设有一条记录如下:Ein Artikel, der kurz analysiert, wie MySQL das Phantomleseproblem löst插入该记录的事务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日志都连起来,串成一个链表,所以现在的情况就像下图一样:

Ein Artikel, der kurz analysiert, wie MySQL das Phantomleseproblem löst  对该记录每次更新后,都会将旧值放到一条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,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:Ein Artikel, der kurz analysiert, wie MySQL das Phantomleseproblem löst

  • 当记录的事物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: Transaktions-ID. Wenn eine Transaktion einen Zeilendatensatz ändert, wird die Transaktions-ID dieser Transaktion zugewiesen zu dieser Spalte

  • roll_pointer: Jedes Mal, wenn Änderungen an Zeilendatensätzen vorgenommen werden, wird die alte Version der Daten in das Undolog-Protokoll geschrieben, und dann zeigt roll_pointer auf den Undolog, daher entspricht diese Spalte einem übergebenen Zeiger. In dieser Spalte finden Sie die Informationen vor der Änderung🎜<h2 data-id="heading-1"> <strong>2 MVCC-Details</strong> </h2> <h3 data-id="heading-2"> <strong>2.1 Versionskette</strong> </h3>🎜Angenommen, es gibt einen Datensatz wie folgt: <img src="https://img.%20php.cn/upload/article/000/000/024/a3789b0f8f8d61a35c454c5a61c58645-2.%20png" alt="Ein Artikel, der kurz analysiert, wie MySQL das Phantomleseproblem löst" loading="lazy">Die in diesen Datensatz eingefügte <code>Transaktions-ID ist 80 und der Zeiger roll_pointer ist NULL (Zum leichteren Verständnis können Leser verstehen, dass der Zeiger NULL ist. Tatsächlich markiert das erste Bit von roll_pointer den Typ des Rückgängig-Protokolls es zeigt auf. Wenn der Wert dieses Bits 1 ist, stellt es das Rückgängig-Protokoll dar, auf das es zeigt 100 und 200, um diesen Datensatz zu AKTUALISIERENVorgang: 🎜rrreee🎜  Jedes Mal, wenn ein Datensatz geändert wird, wird ein Rückgängig-Protokoll aufgezeichnet , und jedes Rückgängig-Protokoll hat auch ein roll_pointer-Code>-Attribut (das Rückgängig-Protokoll, das der INSERT-Operation entspricht, tut dies). nicht über dieses Attribut verfügen, da der Datensatz keine frühere Version hat), sind diese Rückgängig-Protokollealle miteinander verbunden und in einer verknüpften Liste aufgereiht, sodass die aktuelle Situation wie im Bild unten aussieht: 🎜🎜Ein Artikel, der kurz analysiert, wie MySQL das Phantomleseproblem löst Daher jedes Mal, wenn der Datensatz aktualisiert wird , wird der alte Wert in einem Rückgängig-Protokoll abgelegt, auch wenn es sich um einen alten Wert der Datensatzversion handelt. Mit zunehmender Anzahl von Aktualisierungen werden alle Versionen durch das roll_pointer-Attribut. Wir nennen diese verknüpfte Liste Versionskette, den Kopfknoten der Versionskette. Es ist der neueste Wert des aktuellen Datensatzes. Darüber hinaus enthält jede Version auch die entsprechende Transaktions-ID🎜

    2.2 ReadView

    🎜 Für die vier Isolationsstufen der Datenbank: 1)nicht festgeschrieben; 2) REPEATABLE READ; ; Beispielsweise muss READ UNCOMMITTED jedes Mal nur die neuesten Daten der Versionskette lesen; SERIALIZABLE wird hauptsächlich durch Sperren und read commitgesteuert > und REPEATABLE READ lesen beide festgeschriebene Dinge, daher besteht die Kernfrage darin, welche Dinge in der Versionskette für das aktuelle Ding sichtbar sind, um dieses Problem zu lösen Konzept der Leseansicht, das vier Kernkonzepte enthält: 🎜
    • m_ids: Beim Generieren von Leseansicht wird die aktive Ding-ID festgelegt 🎜
    • min_trx_id: Der Mindestwert von m_ids, der der Mindestwert aktiver Dinge beim Generieren einer Leseansicht ist🎜
    • max_trx_id: Zeigt an, dass beim Generieren von Ansicht lesen, das System sollte den nächsten Ding-ID-Wert zuweisen 🎜
    • creator_trx_id: Erstellt die Ding-ID von Ansicht lesen, das heißt aktuelle Ding-ID. 🎜🎜🎜Mit diesem ReadView müssen Sie beim Zugriff auf einen Datensatz nur die folgenden Schritte ausführen, um festzustellen, ob eine bestimmte Version des Datensatzes sichtbar ist: Ein Artikel, der kurz analysiert, wie MySQL das Phantomleseproblem löst🎜
      • Wenn die aufgezeichnete Ding-ID gleich creator_trx_id bedeutet, dass das aktuelle Ding auf seinen eigenen geänderten Datensatz zugreift, sodass diese Version sichtbar ist🎜
      • Wenn die Ding-ID der aufgerufenen Version kleiner als min_trx_id ist, bedeutet dies dass beim Erstellen der read view das Ding übermittelt wurde und diese Version für das aktuelle Ding lesbar ist🎜
      • Wenn die Ding-ID der aufgerufenen Version größer oder gleich max_trx_id bedeutet, dass es erstellt wird, wenn <code>Leseansicht generiert wird. Dies bedeutet, dass die Ding-ID, die diesen Versionsdatensatz generiert, erst geöffnet wird, wenn Leseansicht generiert wird Diese Version kann vom aktuellen Ding nicht gelesen werden🎜
      • Wenn sich die aufgerufene Versionstransaktion transaction_id in der m_ids-Sammlung befindet, bedeutet dies, dass bei Read view code> wird generiert, die Transaktion ist noch aktiv und wurde noch nicht übermittelt. Wenn nicht, bedeutet dies, dass die Transaktion, die diese Version beim Erstellen von <code>ReadView generiert hat, übermittelt wurde und kann abgerufen werden🎜🎜🎜Hinweis: Die Ding-ID des gelesenen Dings ist 0🎜

        In MySQL besteht ein sehr großer Unterschied zwischen den Isolationsstufen von READ COMMITTED und REPEATABLE READ darin, dass sie ReadView zu unterschiedlichen Zeiten generieren: 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行记录的版本链如下:

        Ein Artikel, der kurz analysiert, wie MySQL das Phantomleseproblem löst在时间⑥中,因事务trx 200 执行了事务的提交,id=1行记录的版本链如下:

        Ein Artikel, der kurz analysiert, wie MySQL das Phantomleseproblem löst

        在时间⑤,事务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 —— Generieren Sie jedes Mal einen ReadView, bevor Sie Daten lesen.
        • REPEATABLE READ —— Generieren Sie beim ersten Lesen von Daten einen ReadView
      Verwenden wir detaillierte Beispiele, um den Unterschied zwischen den beiden zu veranschaulichen:

      Time number trx 100 trx 200
      ① td> BEGIN;


      BEGIN; BEGIN;

      update person set grade =20 where id =1;

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


      COMMIT;

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



      COMMIT;
      ? COMMIT;

      at Zum Zeitpunkt ④, fällig Für die Transaktion trx 100, die die Transaktionsübermittlung ausführt, lautet die in der Zeile „id=1“ aufgezeichnete Versionskette wie folgt:

      Ein Artikel, der kurz analysiert, wie MySQL das Phantomleseproblem löstZum Zeitpunkt ⑥, aufgrund der Transaktion trx 200, die die Transaktionsübermittlung ausführt, Die Version Die Kette des ID=1-Zeilendatensatzes lautet wie folgt:

      image. png🎜🎜Wenn zum Zeitpunkt ⑤ die Transaktion trx 100 die select-Anweisung ausführt, wird zunächst ein ReadViewgeneriert >, Der Inhalt der <code>m_ids-Liste von ReadView ist [100, 200], min_trx_id ist 100, max_trx_id ist 201 und creator_trx_id ist 0. Wählen Sie zu diesem Zeitpunkt das Sichtbare aus Datensatz aus der Versionskette. Die Versionskette beginnt von oben. Gehen Sie zum nächsten über: Da grade=40 ist, ist der Wert von trx_id 100, was in liegt m_ids, daher ist der Datensatz unsichtbar, grade= Die unter 20 fehlen ebenfalls. Gehen Sie weiter nach unten, Grad = 20, der Wert von trx_id ist 80, was kleiner ist als der Wert von min_trx_idReadView code>100, sodass diese Version die Anforderungen erfüllt und Datensätze mit Level 10 an den Benutzer zurückgegeben werden. 🎜🎜Wenn zum Zeitpunkt ⑧ die Isolationsstufe der Transaktion READ COMMITTED ist, wird ein separater ReadView generiert und der dieses <code>ReadView Der Inhalt der m_ids-Liste ist [200], min_trx_id ist 200 und max_trx_id code> ist <code> 201, creator_trx_id ist 0. Wählen Sie zu diesem Zeitpunkt den sichtbaren Datensatz aus der Versionskette aus und durchlaufen Sie die Versionskette von oben nach unten: Da grade=70, ist der Wert von trx_id 200, was in m_ids liegt, sodass der Datensatz nicht sichtbar ist. grade=40,trx_id >Der Wert ist 100, was kleiner ist als der min_trx_id-Wert 200 in ReadView, damit diese Version die Anforderungen erfüllt, return Was dem Benutzer übergeben wird, ist ein Datensatz mit Level 40. 🎜🎜In Zeit ⑧, wenn die Isolationsstufe der Transaktion REPEATABLE READ ist, wird in Zeit ⑧ kein separater ReadView generiert, sondern der ReadView verwendet, sodass der an den Benutzer zurückgegebene Level 10 ist. Das Ergebnis der beiden Auswahlen ist das gleiche. Dies ist die Bedeutung von wiederholbares Lesen. 🎜

      🎜3. Zusammenfassung🎜

      🎜  Durch die Analyse der detaillierten Erklärung von MVCC kann der Schluss gezogen werden, dass auf MVCC unter der RR-Isolationsstufe der Phantom ist ein gut gelöstes Leseproblem, aber wir wissen, dass <code>select for update den aktuellen Lesevorgang generiert und kein Snapshot-Lesevorgang mehr ist. Wie löst MySQL in diesem Fall den Phantom-Lesevorgang? /code> >Was ist mit dem Problem? Aufgrund des Zeitproblems (das Sortieren der Zeichnung nimmt viel Zeit in Anspruch) werde ich hier zunächst eine Schlussfolgerung ziehen und dann analysieren, wie MySQL das <code>Phantom Read-Problem unter der aktuellen Lesesituation löst : 🎜🎜🎜🎜 Aktueller Lesevorgang🎜: Verwenden Sie die Next-Key-Sperre (Lückensperre) zum Sperren, um sicherzustellen, dass keine Phantom-Lesevorgänge auftreten. 🎜🎜🎜Wie die Lückensperre das Phantom-Leseproblem in der aktuellen Lesesituation löst, können interessierte Freunde erfahren Fügen Sie einen Follow hinzu. Like🎜🎜[Verwandte Empfehlungen: 🎜MySQL-Video-Tutorial🎜]🎜

Das obige ist der detaillierte Inhalt vonEin Artikel, der kurz analysiert, wie MySQL das Phantomleseproblem löst. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.cn. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen