MVCC (Multi-Version Concurrency Control)

The full name is Multi-Version Concurrency Control, that is, multi-version concurrency control, mainly to improve the concurrency performance of the database. When a read or write request occurs for the same row of data, it will be locked and blocked. But MVCC uses a better way to handle read-write requests, so that there is no need to lock when read-write request conflicts occur. This read refers to the snapshot read, not the current read. The current read is a locking operation, which is a pessimistic lock. So how does it achieve read-write without locking, what do snapshot read and current read refer to? We will all learn later.

1. Principle of MVCC

1.1, review transaction isolation level

Under the REPEATABLE READ isolation level, MySQL can largely avoid the phantom reading problem ( it seems to have been solved, but it has not been completely solved ). How does MySQL do it?

1.2, version chain

Concepts that must be known (one piece of data for each version chain):

We know that for a table using the InnoDB storage engine, its clustered index records contain two necessary hidden columns (row_id is not necessary, when the table we created has a primary key or a non-NULL UNIQUE key The row_id column will not be included): trx_id: Every time a transaction modifies a clustered index record, the transaction id of the transaction will be assigned to the trx_id hidden column. roll_pointer: Every time a clustered index record is modified, the old version will be written into the undo log, and then this hidden column is equivalent to a pointer, through which the information before the modification of the record can be found.

(Supplementary point: undo log: In order to achieve the atomicity of transactions, when the InnoDB storage engine actually adds, deletes, and modifies a record, it needs to write down the corresponding undo log first. Generally, every time a record is changed, it will Corresponds to one undo log , but in some operations of updating records, it may also correspond to two undo logs. A transaction may add, delete, and update several records during execution, that is to say, many records need to be recorded Corresponding undo logs, these undo logs will be numbered from 0, that is to say, according to the order of generation, they are called No. 0 undo log, No. 1 undo log, ..., No. n undo log, etc., this Numbering is also known as undo no.)

To illustrate this, we create a demo table

CREATE TABLE teacher (
number INT,
name VARCHAR(100),
domain varchar(100),
PRIMARY KEY (number)
) Engine=InnoDB CHARSET=utf8;

Then insert a piece of data into this table:

INSERT INTO teacher VALUES(1, 'Li Jin', 'JVM series');

The data in the table now looks like this:

Assuming that the transaction id of inserting this record is 60, the schematic diagram of this record at this moment is as follows:

Assume that the next two transactions whose transaction IDs are 80 and 120 respectively perform UPDATE operations on this record, and the operation process is as follows:

Every time a record is changed, an undo log will be recorded, and each undo log also has a roll_pointer attribute (the undo log corresponding to the INSERT operation does not have this attribute, because the record does not have an earlier version), and these undo logs can be They are all connected together to form a linked list, so the current situation looks like the following picture:

Every time the record is updated, the old value will be put into an undo log. Even if it is an old version of the record, as the number of updates increases, all versions will be connected into a linked list by the roll_pointer attribute. We put this The linked list is called a version chain, and the head node of the version chain is the latest value of the current record. In addition, each version also contains the corresponding transaction id when the version was generated. So the version chain of this record can be used to control the behavior of concurrent transactions accessing the same record, then this mechanism is called Multi-Version Concurrency Control (Mulit-Version Concurrency Control MVCC).

1.3,ReadView

Concepts that must be known (for SQL query statements)

For transactions using the READ UNCOMMITTED isolation level, since the records modified by uncommitted transactions can be read, it is good to directly read the latest version of the records (so there will be dirty reads, non-repeatable reads, and phantom reads ).

For transactions using the SERIALIZABLE isolation level, InnoDB uses locking to access records ( that is, all transactions are serial, and of course there will be no dirty reads, non-repeatable reads, or phantom reads ).

For transactions using the READ COMMITTED and REPEATABLE READ isolation levels, it is necessary to ensure that the records modified by the submitted transaction are read. That is to say, if another transaction has modified the record but has not yet committed it, it cannot directly read the latest For version records, the core question is: where does the difference between READ COMMITTED and REPEATABLE READ isolation levels come from in terms of non-repeatable reads and phantom reads? In fact, combined with the previous knowledge, the key to these two isolation levels is to judge the version chain Which version in is visible to the current transaction . To this end, InnoDB proposes a concept of ReadView (acting on SQL query statements),

This ReadView mainly contains 4 more important contents: m_ids: Indicates the transaction id list of the active read and write transactions in the current system when the ReadView is generated. min_trx_id: Indicates the smallest transaction id among the active read and write transactions in the current system when ReadView is generated, that is, the minimum value in m_ids. max_trx_id: Indicates the id value that should be assigned to the next transaction in the system when ReadView is generated. Note that max_trx_id is not the maximum value in m_ids, and transaction ids are allocated incrementally. For example, now there are three transactions with id 1, 2, and 3, and then the transaction with id 3 is submitted. Then when a new read transaction generates ReadView, m_ids includes 1 and 2, the value of min_trx_id is 1, and the value of max_trx_id is 4. creator_trx_id: Indicates the transaction id of the transaction that generated the ReadView.

1.4,READ COMMITTED

Dirty read problem solving

READ COMMITTED isolation level transactions will generate a separate ReadView at the beginning of each query.

In MySQL, a very big difference between READ COMMITTED and REPEATABLE READ isolation levels is that they generate ReadView at different times. Let's still take the table teacher as an example. Assuming that there is only one record inserted by a transaction with a transaction id of 60 in the table teacher, let's take a look at the difference in the timing of generating ReadView between READ COMMITTED and REPEATABLE READ. READ COMMITTED —— Generate a ReadView every time before reading data

UPDATE teacher  SET name = '马' WHERE number = 1;
UPDATE teacher  SET name = '连' WHERE number = 1;
...

At this moment, the version linked list obtained by the record whose number is 1 in the table teacher is as follows:

Assuming that a transaction using the READ COMMITTED isolation level is now executed:

Transaction using READ COMMITTED isolation level 
BEGIN
; 
SELECE1: Transaction 80, 120 uncommitted 
SELECT
* FROM teacher WHERE number = 1; # The value of the obtained column name is 'Li Jin'

The timing of the first selection is as follows:

The execution process of this SELECE1 is as follows: When executing the SELECT statement, a ReadView will be generated first:

The content of ReadView's m_ids list is [80, 120], min_trx_id is 80, max_trx_id is 121, and creator_trx_id is 0.

Then select the visible records from the version chain. It can be seen from the figure that the content of the column name of the latest version is ' connect ', and the trx_id value of this version is 80, which is in the m_ids list, so it does not meet the visibility requirements (trx_id If the attribute value is between the min_trx_id and max_trx_id of ReadView, it means that the transaction that generated this version when ReadView was created is still active, and this version cannot be accessed; if it is not, it means that the transaction that generated this version when ReadView was created has been submitted, and this version can be accessed Access), skip to the next version according to roll_pointer. The content of the column name in the next version is ' horse ', and the trx_id value of this version is also 80, which is also in the m_ids list, so it does not meet the requirements, so continue to skip to the next version. The content of the column name in the next version is ' Li Jin ', the trx_id value of this version is 60, which is smaller than the min_trx_id value in ReadView, so this version meets the requirements, and the final version returned to the user is the column name of ' Lee Jin ' record.

So with this mechanism, there will be no dirty read problem! Because it will judge the active version, it must be used if it is not in the active version, and it is impossible to read records without commits.

non-repeatable read problem

Then, we submit the transaction with the transaction id of 80, and then update the record with the number 1 in the table teacher in the transaction with the transaction id of 120:

Transaction120 
​BEGIN
; 
​Update
some records of other tables 
​UPDATE
teacher SET name = 'Yan' WHERE number = 1; 
UPDATE teacher SET name = 'Chao' WHERE number = 1 
;

At this moment, the version chain of the record whose number is 1 in the table teacher looks like this:

Then go to the transaction that just used the READ COMMITTED isolation level to continue to find the record with the number 1, as follows:

Transactions using the READ COMMITTED isolation level

BEGIN; 
​SELECE1
: Transaction 80 and 120 are not submitted 
​SELECT
* FROM teacher WHERE number = 1; # The value of the obtained column name is 'Li Jin' 
​SELECE2
: Transaction 80 is submitted, Transaction 120 is not submitted 
​SELECT
* FROM teacher WHERE number = 1; # The value of the obtained column name is 'connected 
'

The timing of the second selection is as follows:

The execution process of this SELECE2 is as follows:

SELECT * FROM teacher WHERE number = 1;

When the SELECT statement is executed, a ReadView will be generated separately, and the ReadView information is as follows:

The content of the m_ids list is [120] (the transaction whose transaction id is 80 has been submitted, so it will not be there when the snapshot is generated again), min_trx_id is 120, max_trx_id is 121, and creator_trx_id is 0. Then select the visible records from the version chain. It can be seen from the figure that the content of the column name of the latest version is '', and the trx_id value of this version is 120, which is in the m_ids list, so it does not meet the visibility requirements. According to roll_pointer skips to the next version. The content of the column name in the next version is ' strict ', and the trx_id value of this version is 120, which is also in the m_ids list, so it does not meet the requirements, so continue to skip to the next version. The content of the column name in the next version is ' connect ', the trx_id value of this version is 80, which is smaller than the min_trx_id value of 120 in ReadView, so this version meets the requirements, and the final version returned to the user is the column name of ' even ' records.

By analogy, if the record whose transaction id is 120 is also submitted later, when querying the record whose number value is 1 in the table teacher in the transaction using the READ COMMITTED isolation level again, the result is '晓', the specific process we I won't analyze it.

But there will be non-repeatable read problem.

Obviously twice in the above transaction

1.5,REPEATABLE READ

REPEATABLE READ solves non-repeatable read problem

REPEATABLE READ - generate a ReadView the first time data is read

For transactions using the REPEATABLE READ isolation level, a ReadView will only be generated when the query statement is executed for the first time, and subsequent queries will not be generated repeatedly. Let's use an example to see what the effect is.

For example, now there are two transactions with transaction IDs of 80 and 120 in the system: Transaction 80

UPDATE teacher  SET name = '马' WHERE number = 1;
UPDATE teacher  SET name = '连' WHERE number = 1;
...

At this moment, the version linked list obtained by the record whose number is 1 in the table teacher is as follows:

Assuming that a transaction using the REPEATABLE READ isolation level is now executed:

Transaction using READ COMMITTED isolation level 
BEGIN
; 
SELECE1: Transaction 80, 120 uncommitted 
SELECT
* FROM teacher WHERE number = 1; # The value of the obtained column name is 'Li Jin'

The execution process of this SELECE1 is as follows: When executing the SELECT statement, a ReadView will be generated first:

The content of ReadView's m_ids list is [80, 120], min_trx_id is 80, max_trx_id is 121, and creator_trx_id is 0.

Then select the visible records from the version chain. It can be seen from the figure that the content of the column name of the latest version is ' connect ', and the trx_id value of this version is 80, which is in the m_ids list, so it does not meet the visibility requirements (trx_id If the attribute value is between the min_trx_id and max_trx_id of ReadView, it means that the transaction that generated this version when ReadView was created is still active, and this version cannot be accessed; if it is not, it means that the transaction that generated this version when ReadView was created has been submitted, and this version can be accessed Access), skip to the next version according to roll_pointer. The content of the column name in the next version is ' horse ', and the trx_id value of this version is also 80, which is also in the m_ids list, so it does not meet the requirements, so continue to skip to the next version. The content of the column name in the next version is ' Li Jin ', the trx_id value of this version is 60, which is smaller than the min_trx_id value in ReadView, so this version meets the requirements, and the final version returned to the user is the column name of ' Lee Jin ' record. After that, we submit the transaction with transaction id 80, and then update the record with number 1 in the table teacher in the transaction with transaction id 120:

Transaction120 
​BEGIN
; 
​Update
some records of other tables 
​UPDATE
teacher SET name = 'Yan' WHERE number = 1; 
UPDATE teacher SET name = 'Chao' WHERE number = 1 
;

At this moment, the version chain of the record whose number is 1 in the table teacher looks like this:

Then go to the transaction that just used the REPEATABLE READ isolation level to continue to find the record with the number 1, as follows:

Transactions using the READ COMMITTED isolation level

BEGIN; 
​SELECE1
: Transaction 80 and 120 are not submitted 
​SELECT
* FROM teacher WHERE number = 1; # The value of the obtained column name is 'Li Jin' 
​SELECE2
: Transaction 80 is submitted, Transaction 120 is not submitted 
​SELECT
* FROM teacher WHERE number = 1; # The value of the obtained column name is 'Li Jin 
'

The execution process of this SELECE2 is as follows:

Because the isolation level of the current transaction is REPEATABLE READ, and ReadView has been generated before executing SELECE1, so at this time, the previous ReadView is directly reused. The content of the m_ids list of the previous ReadView is [80, 120], and the min_trx_id is 80 , max_trx_id is 121, creator_trx_id is 0.

According to the previous analysis, the returned value is still 'Li Jin'.

That is to say, the results of the two SELECT queries are repeated, and the column name values ​​of the records are all 'Li Jin', which is the meaning of repeatable reading.

To sum it up:

Comparison rules in ReadView (the first two)

1. If the trx_id attribute value of the accessed version is the same as the creator_trx_id value in ReadView, it means that the current transaction is accessing its own modified records, so this version can be accessed by the current transaction.

2. If the trx_id attribute value of the accessed version is less than the min_trx_id value in ReadView, it indicates that the transaction that generated this version has been committed before the current transaction generates ReadView, so this version can be accessed by the current transaction.

1.6, Phantom reading solution and phantom reading phenomenon under MVCC

We already know that MVCC under the REPEATABLE READ isolation level can solve the non-repeatable read problem, so what about phantom reads? How is MVCC solved? Phantom reading is when a transaction reads records multiple times according to the same condition, and then reads a record that has not been read before, and this record comes from a new record added by another transaction. We can think about it, transaction T1 under the REPEATABLE READ isolation level first reads multiple records according to a certain search condition, then transaction T2 inserts a record that meets the corresponding search condition and commits, and then transaction T1 executes according to the same search condition Inquire. What will be the result? According to the comparison rules in ReadView (the last two): 3. If the trx_id attribute value of the accessed version is greater than or equal to the max_trx_id value in ReadView, it means that the transaction that generates this version is started after the current transaction generates ReadView, so this version does not Can be accessed by the current transaction. 4. If the trx_id attribute value of the accessed version is between the min_trx_id and max_trx_id of ReadView (min_trx_id < trx_id < max_trx_id), then you need to judge whether the trx_id attribute value is in the m_ids list. If it is, it means that it was generated when ReadView was created The transaction of the version is still active, and this version cannot be accessed; if not, it means that the transaction that generated this version has been committed when ReadView was created, and this version can be accessed.

Regardless of whether transaction T2 is opened before transaction T1, transaction T1 cannot see the commit of T2. Please analyze it by yourself according to the version chain, ReadView and the rules for judging visibility introduced above. However, under the REPEATABLE READ isolation level, MVCC in InnoDB can largely avoid phantom reading, rather than completely prohibiting phantom reading. What's going on? Let's look at the following situation:

We first in transaction T1:

select * from teacher where number = 30;

Obviously, the record with number = 30 cannot be found at this time. In transaction T2, we execute:

insert into teacher values(30,'Leopard','data lake');

By executing insert into teacher values(30,'Leopard','Data Lake');, we inserted a record with number = 30 into the table. At this time, go back to transaction T1 and execute:

update teacher set domain='RocketMQ' where number=30;
select * from teacher where number = 30;

Well, what's going on? Transaction T1 obviously has a phantom read phenomenon. Under the REPEATABLE READ isolation level, T1 generates a ReadView when it executes a normal SELECT statement for the first time (but the version chain does not), then T2 inserts a new record into the teacher table and submits it, and then T1 also performs an update statement. ReadView cannot prevent T1 from executing UPDATE or DELETE statements to change the newly inserted record, but in this way, the value of the hidden column trx_id of this new record becomes the transaction id of T1.

Afterwards, when T1 uses the ordinary SELECT statement to query this record, it can see this record, and it can return this record to the client. Because of the existence of this special phenomenon, we can also think that MVCC cannot completely prohibit phantom reading ( that is, if the first read is empty, and the data is modified in its own transaction ).

1.7, MVCC summary

From the above description, we can see that the so-called MVCC (Multi-Version Concurrency Control, multi-version concurrency control) refers to the use of two isolation levels of READ COMMITTD and REPEATABLE READ to access when performing ordinary SELECT operations. The process of the recorded version chain, so that the read-write and write-read operations of different transactions can be executed concurrently, thereby improving system performance.

A big difference between the two isolation levels of READ COMMITTD and REPEATABLE READ is that the timing of generating ReadView is different. READ COMMITTD will generate a ReadView before each ordinary SELECT operation, while REPEATABLE READ only performs ordinary SELECT operations for the first time. Generate a ReadView before, and then reuse this ReadView for subsequent query operations, so that phantom reading can basically be avoided (that is, if the ReadView is empty for the first time, it cannot be avoided in some cases ).

In addition, the so-called MVCC only takes effect when we perform ordinary SEELCT queries. All the SELECT statements we have seen so far are ordinary queries. As for what is an unusual query, it is (locked read).

Guess you like

Origin blog.csdn.net/m0_70299172/article/details/130477162